Share via


Retribuzione

La compensazione di Windows Workflow Foundation (WF) è il meccanismo mediante il quale il lavoro completato in precedenza può essere annullato o compensato (seguendo la logica definita dall'applicazione) quando si verifica un errore successivo. Contenuto della sezione viene illustrato come usare la compensazione nei flussi di lavoro.

Differenze tra compensazione e transazioni

Una transazione consente di combinare più operazioni in un'unica unità di lavoro. Quando viene usata una transazione, l'applicazione può annullare, ovvero eseguire il rollback, di tutte le modifiche eseguite dall'interno della transazione se si verificano errori durante qualsiasi parte del processo della transazione. L'utilizzo di transazioni potrebbe tuttavia non essere adatto per un lavoro a esecuzione prolungata. Ad esempio, un'applicazione di pianificazione di viaggi viene implementata come flusso di lavoro. I passaggi del flusso di lavoro possono essere costituiti dalla prenotazione di un volo, dall'attesa dell'approvazione da parte del responsabile e dal pagamento del volo. Questo processo potrebbe richiedere molti giorni e non è funzionale che i passaggi di prenotazione e pagamento del volo prendano parte alla stessa transazione. In uno scenario come questo, la compensazione potrebbe essere usata per annullare il passaggio di prenotazione del flusso di lavoro se, successivamente, si verifica un errore nell'elaborazione.

Nota

In questo argomento viene illustrato il concetto di compensazione nei flussi di lavoro. Per altre informazioni sulle transazioni nei flussi di lavoro, vedere Transazioni e TransactionScope. Per altre informazioni sulle transazioni, vedere System.Transactions e System.Transactions.Transaction.

Uso di CompensableActivity

La classe CompensableActivity è l'attività di compensazione principale di WF. Qualsiasi attività che esegue un lavoro che necessita di compensazione viene inserita nell'oggetto Body di un oggetto CompensableActivity. In questo esempio il passaggio di prenotazione relativo all'acquisto di un volo viene inserito nell'oggetto Body di un oggetto CompensableActivity, mentre l'annullamento della prenotazione viene inserito nell'oggetto CompensationHandler. Subito dopo l'oggetto CompensableActivity nel flusso di lavoro si trovano due attività che attendono l'approvazione del responsabile e successivamente completano il passaggio di acquisto del volo. Se una condizione di errore provoca l'annullamento del flusso di lavoro dopo il corretto completamento dell'oggetto CompensableActivity, le attività nel gestore CompensationHandler vengono pianificate e il volo viene annullato.

Activity wf = new Sequence()
{
    Activities =
    {
        new CompensableActivity
        {
            Body = new ReserveFlight(),
            CompensationHandler = new CancelFlight()
        },
        new ManagerApproval(),
        new PurchaseFlight()
    }
};

L'esempio seguente è il flusso di lavoro in XAML.

<Sequence
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <CompensableActivity>
    <CompensableActivity.Result>
      <OutArgument
         x:TypeArguments="CompensationToken" />
    </CompensableActivity.Result>
    <CompensableActivity.CompensationHandler>
      <c:CancelFlight />
    </CompensableActivity.CompensationHandler>
    <c:ReserveFlight />
  </CompensableActivity>
  <c:ManagerApproval />
  <c:PurchaseFlight />
</Sequence>

Quando il flusso di lavoro viene richiamato, l'output seguente viene visualizzato nella console.

ReserveFlight: il biglietto è stato prenotato.ManagerApproval: è stata ricevuta l'approvazione del responsabile.PurchaseFlight: il biglietto è stato acquistato.Flusso di lavoro completato con stato: Chiuso.

Nota

Nelle attività di esempio in questo argomento, quale ReserveFlight, vengono visualizzati nome e scopo nella console per consentire di illustrare l'ordine in cui vengono eseguite le attività quando si verifica la compensazione.

Compensazione del flusso di lavoro predefinita

Per impostazione predefinita, se il flusso di lavoro viene annullato, viene eseguita la logica di compensazione per qualsiasi attività compensabile che è stata completata correttamente e non è stata già confermata o compensata.

Nota

Quando una classe CompensableActivity viene confermata, la compensazione per l'attività non può essere più richiamata. Il processo di conferma verrà illustrato più avanti in questa sezione.

In questo esempio viene generata un'eccezione dopo la prenotazione del volo ma prima del passaggio di approvazione da parte del responsabile.

Activity wf = new Sequence()
{
    Activities =
    {
        new CompensableActivity
        {
            Body = new ReserveFlight(),
            CompensationHandler = new CancelFlight()
        },
        new SimulatedErrorCondition(),
        new ManagerApproval(),
        new PurchaseFlight()
    }
};

In questo esempio viene illustrato il flusso di lavoro in XAML.

<Sequence
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <CompensableActivity>
    <CompensableActivity.Result>
      <OutArgument
         x:TypeArguments="CompensationToken" />
    </CompensableActivity.Result>
    <CompensableActivity.CompensationHandler>
      <c:CancelFlight />
    </CompensableActivity.CompensationHandler>
    <c:ReserveFlight />
  </CompensableActivity>
  <c:SimulatedErrorCondition />
  <c:ManagerApproval />
  <c:PurchaseFlight />
</Sequence>
AutoResetEvent syncEvent = new AutoResetEvent(false);
WorkflowApplication wfApp = new WorkflowApplication(wf);

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.TerminationException != null)
    {
        Console.WriteLine("Workflow terminated with exception:\n{0}: {1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else
    {
        Console.WriteLine("Workflow completed successfully with status: {0}.",
            e.CompletionState);
    }

    syncEvent.Set();
};

wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
    Console.WriteLine("Workflow Unhandled Exception:\n{0}: {1}",
        e.UnhandledException.GetType().FullName,
        e.UnhandledException.Message);

    return UnhandledExceptionAction.Cancel;
};

wfApp.Run();
syncEvent.WaitOne();

Quando viene richiamato il flusso di lavoro, l'eccezione della condizione di errore simulata viene gestita dall'applicazione host in OnUnhandledException, il flusso di lavoro viene annullato e la logica di compensazione viene richiamata.

ReserveFlight: il biglietto è stato prenotato.SimulatedErrorCondition: viene generata una ApplicationException.Eccezione del flusso di lavoro non gestita:System.ApplicationException: condizione di errore simulata nel flusso di lavoro.CancelFlight: il biglietto è stato annullato.Flusso di lavoro completato con stato: Annullato.

Annullamento e CompensableActivity

Se le attività in Body di un oggetto CompensableActivity non sono state completate e l'attività è annullata, vengono eseguite le attività in CancellationHandler.

Nota

CancellationHandler viene richiamato solo se le attività nell'oggetto Body dell'oggetto CompensableActivity non sono state completate e l'attività è annullata. CompensationHandler viene eseguito solo se le attività nell'oggetto Body dell'oggetto CompensableActivity sono state completate correttamente e viene successivamente richiamata la compensazione sull'attività.

CancellationHandler fornisce offre agli autori del flusso di lavoro la possibilità per fornire la logica di annullamento appropriata. Nell'esempio seguente viene generata un'eccezione durante l'esecuzione di Body, quindi viene richiamato CancellationHandler.

Activity wf = new Sequence()
{
    Activities =
    {
        new CompensableActivity
        {
            Body = new Sequence
            {
                Activities =
                {
                    new ChargeCreditCard(),
                    new SimulatedErrorCondition(),
                    new ReserveFlight()
                }
            },
            CompensationHandler = new CancelFlight(),
            CancellationHandler = new CancelCreditCard()
        },
        new ManagerApproval(),
        new PurchaseFlight()
    }
};

Questo esempio è il flusso di lavoro in XAML

<Sequence
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <CompensableActivity>
    <CompensableActivity.Result>
      <OutArgument
         x:TypeArguments="CompensationToken" />
    </CompensableActivity.Result>
    <Sequence>
      <c:ChargeCreditCard />
      <c:SimulatedErrorCondition />
      <c:ReserveFlight />
    </Sequence>
    <CompensableActivity.CancellationHandler>
      <c:CancelCreditCard />
    </CompensableActivity.CancellationHandler>
    <CompensableActivity.CompensationHandler>
      <c:CancelFlight />
    </CompensableActivity.CompensationHandler>
  </CompensableActivity>
  <c:ManagerApproval />
  <c:PurchaseFlight />
</Sequence>

Quando viene richiamato il flusso di lavoro, l'eccezione della condizione di errore simulata viene gestita dall'applicazione host in OnUnhandledException, il flusso di lavoro viene annullato e viene richiamata la logica di cancellazione di CompensableActivity. In questo esempio, la logica di compensazione e la logica di annullamento hanno obiettivi differenti. Se Body è stato completato correttamente, questo significa che è stato effettuato il prelievo dalla carta di credito e il volo è stato prenotato, pertanto la compensazione dovrebbe annullare entrambi i passaggi. In questo esempio, l'annullamento del volo annulla automaticamente l'addebito sulla carta di credito. Tuttavia, se CompensableActivity viene annullato, questo significa che Body non è stato completato e la logica di CancellationHandler deve essere in grado di determinare il metodo migliore di gestire l'annullamento. In questo esempio, CancellationHandler annulla l'addebito sulla carta di credito, tuttavia, poiché ReserveFlight era l'ultima attività in Body, non tenta di annullare il volo. Dal momento che ReserveFlight era l'ultima attività in Body, se fosse stata completata correttamente, l'oggetto Body sarebbe stato completato e non sarebbe stato possibile alcun annullamento.

ChargeCreditCard: addebito sulla carta di credito per il volo.SimulatedErrorCondition: viene generata una ApplicationException.Eccezione del flusso di lavoro non gestita:System.ApplicationException: condizione di errore simulata nel flusso di lavoro.CancelCreditCard: annullamento delle spese della carta di credito.Flusso di lavoro completato con stato: Annullato. Per altre informazioni sull'annullamento, vedere Annullamento.

Compensazione esplicita tramite l'attività Compensate

Nella sezione precedente è stata illustrata la compensazione implicita. Si tratta di un tipo di compensazione che può risultare appropriata per scenari semplici, ma se occorre un controllo più esplicito sulla pianificazione della gestione della compensazione è possibile usare l'attività Compensate. Per iniziare il processo di compensazione con l'attività Compensate, viene usato l'oggetto CompensationToken dell'oggetto CompensableActivity per il quale si desidera la compensazione. L'attività Compensate può essere usata per iniziare la compensazione su qualsiasi oggetto CompensableActivity completato che non è stato confermato o compensato. Ad esempio, un'attività Compensate potrebbe essere usata nella sezione Catches di un'attività TryCatch o in qualsiasi momento dopo il completamento dell'oggetto CompensableActivity. In questo esempio l'attività Compensate viene usata nella sezione Catches di un'attività TryCatch per invertire l'azione dell'oggetto CompensableActivity.

Variable<CompensationToken> token1 = new Variable<CompensationToken>
{
    Name = "token1",
};

Activity wf = new TryCatch()
{
    Variables =
    {
        token1
    },
    Try = new Sequence
    {
        Activities =
        {
            new CompensableActivity
            {
                Body = new ReserveFlight(),
                CompensationHandler = new CancelFlight(),
                ConfirmationHandler = new ConfirmFlight(),
                Result = token1
            },
            new SimulatedErrorCondition(),
            new ManagerApproval(),
            new PurchaseFlight()
        }
    },
    Catches =
    {
        new Catch<ApplicationException>()
        {
            Action = new ActivityAction<ApplicationException>()
            {
                Handler = new Compensate()
                {
                    Target = token1
                }
            }
        }
    }
};

In questo esempio viene illustrato il flusso di lavoro in XAML.

<TryCatch
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:s="clr-namespace:System;assembly=mscorlib"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <TryCatch.Variables>
    <Variable
       x:TypeArguments="CompensationToken"
       x:Name="__ReferenceID0"
       Name="token1" />
  </TryCatch.Variables>
  <TryCatch.Try>
    <Sequence>
      <CompensableActivity>
        <CompensableActivity.Result>
          <OutArgument
             x:TypeArguments="CompensationToken">
            <VariableReference
               x:TypeArguments="CompensationToken"
               Variable="{x:Reference __ReferenceID0}">
              <VariableReference.Result>
                <OutArgument
                   x:TypeArguments="Location(CompensationToken)" />
              </VariableReference.Result>
            </VariableReference>
          </OutArgument>
        </CompensableActivity.Result>
        <CompensableActivity.CompensationHandler>
          <c:CancelFlight />
        </CompensableActivity.CompensationHandler>
        <CompensableActivity.ConfirmationHandler>
          <c:ConfirmFlight />
        </CompensableActivity.ConfirmationHandler>
        <c:ReserveFlight />
      </CompensableActivity>
      <c:SimulatedErrorCondition />
      <c:ManagerApproval />
      <c:PurchaseFlight />
    </Sequence>
  </TryCatch.Try>
  <TryCatch.Catches>
    <Catch
       x:TypeArguments="s:ApplicationException">
      <ActivityAction
         x:TypeArguments="s:ApplicationException">
        <Compensate>
          <Compensate.Target>
            <InArgument
               x:TypeArguments="CompensationToken">
              <VariableValue
                 x:TypeArguments="CompensationToken"
                 Variable="{x:Reference __ReferenceID0}">
                <VariableValue.Result>
                  <OutArgument
                     x:TypeArguments="CompensationToken" />
                </VariableValue.Result>
              </VariableValue>
            </InArgument>
          </Compensate.Target>
        </Compensate>
      </ActivityAction>
    </Catch>
  </TryCatch.Catches>
</TryCatch>

Quando il flusso di lavoro viene richiamato, l'output seguente viene visualizzato nella console.

ReserveFlight: il biglietto è stato prenotato.SimulatedErrorCondition: viene generata una ApplicationException.CancelFlight: il biglietto è annullato.Flusso di lavoro completato con stato: Annullato.

Conferma della compensazione

Per impostazione predefinita, le attività compensabili possono essere compensate in qualsiasi momento una volta completate. In alcuni casi però questo potrebbe non essere appropriato. Nell'esempio precedente la compensazione relativa alla prenotazione del biglietto consisteva nell'annullamento della prenotazione. Tuttavia, una volta completato il volo, questo passaggio di compensazione non è più valido. La conferma dell'attività compensabile richiama l'attività specificata da ConfirmationHandler. Un possibile utilizzo consiste nel consentire a qualsiasi risorsa necessaria di eseguire la compensazione da rilasciare. Una volta confermata, un'attività compensabile non può essere compensata e se si tenta tale operazione, verrà generata un'eccezione InvalidOperationException. Quando un flusso di lavoro viene completato correttamente, tutte le attività compensabili non confermate e non compensate completate correttamente vengono confermate nell'ordine inverso rispetto al completamento. In questo esempio il volo è prenotato, acquistato e completato, quindi l'attività compensabile è confermata. Per confermare un oggetto CompensableActivity, usare l'attività Confirm e specificare l'oggetto CompensationToken dell'oggetto CompensableActivity da confermare.

Variable<CompensationToken> token1 = new Variable<CompensationToken>
{
    Name = "token1",
};

Activity wf = new Sequence()
{
    Variables =
    {
        token1
    },
    Activities =
    {
        new CompensableActivity
        {
            Body = new ReserveFlight(),
            CompensationHandler = new CancelFlight(),
            ConfirmationHandler = new ConfirmFlight(),
            Result = token1
        },
        new ManagerApproval(),
        new PurchaseFlight(),
        new TakeFlight(),
        new Confirm()
        {
            Target = token1
        }
    }
};

In questo esempio viene illustrato il flusso di lavoro in XAML.

<Sequence
   xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Sequence.Variables>
    <x:Reference>__ReferenceID0</x:Reference>
  </Sequence.Variables>
  <CompensableActivity>
    <CompensableActivity.Result>
      <OutArgument
         x:TypeArguments="CompensationToken">
        <VariableReference
           x:TypeArguments="CompensationToken">
          <VariableReference.Result>
            <OutArgument
               x:TypeArguments="Location(CompensationToken)" />
          </VariableReference.Result>
          <VariableReference.Variable>
            <Variable
               x:TypeArguments="CompensationToken"
               x:Name="__ReferenceID0"
               Name="token1" />
          </VariableReference.Variable>
        </VariableReference>
      </OutArgument>
    </CompensableActivity.Result>
    <CompensableActivity.CompensationHandler>
      <c:CancelFlight />
    </CompensableActivity.CompensationHandler>
    <CompensableActivity.ConfirmationHandler>
      <c:ConfirmFlight />
    </CompensableActivity.ConfirmationHandler>
    <c:ReserveFlight />
  </CompensableActivity>
  <c:ManagerApproval />
  <c:PurchaseFlight />
  <c:TakeFlight />
  <Confirm>
    <Confirm.Target>
      <InArgument
         x:TypeArguments="CompensationToken">
        <VariableValue
           x:TypeArguments="CompensationToken"
           Variable="{x:Reference __ReferenceID0}">
          <VariableValue.Result>
            <OutArgument
               x:TypeArguments="CompensationToken" />
          </VariableValue.Result>
        </VariableValue>
      </InArgument>
    </Confirm.Target>
  </Confirm>
</Sequence>

Quando il flusso di lavoro viene richiamato, l'output seguente viene visualizzato nella console.

ReserveFlight: il biglietto è stato prenotato.ManagerApproval: è stata ricevuta l'approvazione del responsabile.PurchaseFlight: il biglietto è astato acquistato.TakeFlight: il volo è stato completato.ConfirmFlight: il volo è stato prenotato e non è più possibile alcun tipo di compensazione.Flusso di lavoro completato con stato: Chiuso.

Annidamento delle attività di compensazione

Un oggetto CompensableActivity può essere posizionato nella sezione Body di un altro oggetto CompensableActivity. Un oggetto CompensableActivity non può essere inserito in un gestore di un altro oggetto CompensableActivity. È responsabilità di un oggetto CompensableActivity padre garantire che quando viene annullato, confermato o compensato, tutte le attività figlio compensabili che sono state completate correttamente e non sono state già confermate o compensate siano confermate o compensate prima dell'annullamento, la conferma o la compensazione del padre. Se questo comportamento non è determinato in modo esplicito, l'oggetto CompensableActivity padre compenserà in modo implicito le attività figlio compensabili se ha ricevuto l'indicazione di annullamento o compensazione. Se il padre ha ricevuto l'indicazione di conferma, confermerà in modo implicito le attività figlio compensabili. Se la logica di gestione di un annullamento, una conferma o una compensazione è modellata in modo esplicito nel gestore dell'oggetto CompensableActivity padre, qualsiasi oggetto figlio non esplicitamente gestito verrà confermato in modo implicito.

Vedi anche