Compartilhar via


Compensação

A compensação no WF (Windows Workflow Foundation) é o mecanismo por que anteriormente tiver terminado o trabalho pode ser desfeito ou compensado (seguindo a lógica definido pelo aplicativo) quando uma falha subsequente ocorre. Esta seção descreve como usar a compensação em fluxos de trabalho.

Compensação versus transações

Uma transação permite que você combine várias operações em uma única unidade de trabalho. Usar uma transação fornece ao aplicativo a capacidade de nulo () para reverter as alterações executadas dentro de transação se qualquer erro ocorre durante qualquer parte do processo de transação. No entanto, usar transações pode não ser adequado se o trabalho é longo. Por exemplo, um aplicativo de planejamento de traço é implementado como um fluxo de trabalho. As etapas de fluxo de trabalho podem consistir registrar um voo, uma aprovação de espera gerente e em seguida, pagar por voo. Esse processo pode levar muitos dias e não é prático para as etapas do registro e pagar por voo para participar na mesma transação. Em um cenário como isso, a compensação pode ser usada para desfazer a etapa de registro de fluxo de trabalho se houver uma falha posteriormente no processamento.

Observação

Este tópico aborda a compensação em fluxos de trabalho. Para obter mais informações sobre transações em fluxos de trabalho, confira Transações e TransactionScope. Para obter mais informações sobre transações, consulte System.Transactions e System.Transactions.Transaction.

Usando CompensableActivity

CompensableActivity é a atividade de compensação central no WF. Todas as atividades que executa o trabalho que precise ser compensado são colocadas em Body de CompensableActivity. Nesse exemplo, a etapa de retorno de comprar um voo é colocada em Body de CompensableActivity e cancelar de fallback é colocado em CompensationHandler. Imediatamente depois de CompensableActivity no fluxo de trabalho são duas atividades aguardando a aprovação gerente e conclua a etapa de compra de voo. Se uma condição de erro faz com que o fluxo de trabalho a ser cancelado após CompensableActivity terminou com êxito, então as atividades no manipulador de CompensationHandler são agendadas e o voo é cancelado.

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

O exemplo a seguir é o fluxo de trabalho em 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 o fluxo de trabalho é chamado, a saída a seguir são exibidas no console.

ReserveFlight: tíquete reservado.ManagerApproval: Aprovação do gerente recebida.PurchaseFlight: tíquete comprado.Fluxo de trabalho concluído com êxito com o status: Fechado.

Observação

As atividades de exemplo neste tópico como ReserveFlight exibem seu nome e finalidade no console ajudar a ilustrar a ordem em que as atividades são executadas quando a compensação ocorre.

Compensação padrão de fluxo de trabalho

Por padrão, se o fluxo de trabalho é cancelado, a lógica de compensação é executada para quaisquer atividades compensável que tenha êxito completamente e não tem sido confirmada ou não foi compensada.

Observação

Quando CompensableActivity é confirmado, a compensação para a atividade não pode mais ser chamada. O processo de confirmação é descrito posteriormente nesta seção.

Nesse exemplo, uma exceção é lançada após o voo é reservado mas antes da etapa de aprovação do gerenciador.

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

Este exemplo é o fluxo de trabalho em 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 o fluxo de trabalho é chamado, a exceção simulada de condição de erro é tratada pelo aplicativo host em OnUnhandledException, o fluxo de trabalho é cancelado, e a lógica de compensação é chamada.

ReserveFlight: tíquete reservado.SimulatedErrorCondition: gerando um ApplicationException.Exceção sem tratamento de fluxo de trabalho:System.ApplicationException: condição de erro simulada no fluxo de trabalho.CancelFlight: tíquete cancelado.Fluxo de trabalho concluído com êxito com o status: Cancelado.

Cancelar e CompensableActivity

Se as atividades em Body de CompensableActivity não foram concluídas e a atividade é cancelada, as atividades em CancellationHandler são executadas.

Observação

CancellationHandler é invocado apenas se as atividades em Body de CompensableActivity não foram concluídas e a atividade é cancelada. CompensationHandler é executado somente se as atividades em Body de CompensableActivity eles concluíram com êxito e a compensação está chamada posteriormente na atividade.

CancellationHandler fornece a autores de fluxo de trabalho a oportunidade de fornecer qualquer lógica apropriada cancelar. No exemplo a seguir, uma exceção é lançada durante a execução de Body, e CancellationHandler é chamado em seguida.

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()
    }
};

Este exemplo é o fluxo de trabalho em 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 o fluxo de trabalho é chamado, a exceção simulada de condição de erro é tratada pelo aplicativo host em OnUnhandledException, o fluxo de trabalho é cancelado, e a lógica de cancelamento de CompensableActivity é chamada. Nesse exemplo, a lógica de compensação e a lógica de cancelamento metas têm diferentes. Se Body concluída com êxito, então isso significa que o cartão de crédito foi carregado e o voo registrado, então a compensação devem desfazer as duas etapas. (Neste exemplo, o cancelamento do voo cancela automaticamente as cobranças de cartão de crédito). No entanto, se o CompensableActivity for cancelado, isso significa que o Body não concluiu e assim que a lógica do CancellationHandler precisará ser capaz de determinar como melhor identificador cancelamento. Nesse exemplo, CancellationHandler cancela a carga de cartão de crédito, mas como ReserveFlight era a atividade a mais recente em Body, não tenta cancelar o voo. Desde que foi ReserveFlight a atividade a mais recente em Body, se tiver terminado com êxito em Body concluiria e nenhum cancelar seria possível.

ChargeCreditCard: Cobrar cartão de crédito para voo.SimulatedErrorCondition: gerando um ApplicationException.Exceção sem tratamento de fluxo de trabalho:System.ApplicationException: condição de erro simulada no fluxo de trabalho.CancelCreditCard: cancelar cobranças de cartão de crédito.Fluxo de trabalho concluído com êxito com o status: Cancelado. Para obter mais informações sobre cancelamento, consulte Cancelamento.

Compensação explícita usando a atividade de compesação

Na seção anterior, a compensação implícita foi abordada. A compensação implícita pode ser adequado para cenários simples, mas se um controle mais explícito é necessário sobre programação de compensação que manipula a atividade de Compensate pode ser usada. Para iniciar o processo de compensação pela atividade de Compensate , CompensationToken de CompensableActivity para que a compensação é desejada é usado. A atividade de Compensate pode ser usada para iniciar a compensação em qualquer CompensableActivity concluído que não foi confirmada ou não foi compensada. Por exemplo, uma atividade de Compensate pode ser usada na seção de Catches de uma atividade de TryCatch , ou após CompensableActivity tiver concluído em qualquer momento que. Nesse exemplo, a atividade de Compensate é usada na seção de Catches de uma atividade de TryCatch para reverter a ação de 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
                }
            }
        }
    }
};

Este exemplo é o fluxo de trabalho em 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 o fluxo de trabalho é chamado, a saída a seguir são exibidas no console.

ReserveFlight: tíquete reservado.SimulatedErrorCondition: gerando um ApplicationException.CancelFlight: tíquete cancelado.Fluxo de trabalho concluído com êxito com o status: Fechado.

Compensação de confirmação

Por padrão, as atividades compensáveis podem ser compensadas em qualquer momento que depois que terminar. Em alguns cenários isso pode não ser adequado. No exemplo anterior a compensação para permitir a permissão foi cancelar a reserva. No entanto, depois que o voo foi concluído essa etapa de compensação não é mais válido. Confirmando a atividade compensável chama a atividade especificada por ConfirmationHandler. Um uso possível para este é permitir todos os recursos que são necessários para executar a compensação a ser liberada. Depois que uma atividade compensável é confirmada não é possível que ele ser compensado, e se esta é tentada uma exceção de InvalidOperationException é lançada. Quando um fluxo de trabalho concluída com sucesso, as atividades compensáveis qualquer não confirmadas e não compensadas que eles concluíram com êxito são confirmadas em ordem inversa de conclusão. Nesse exemplo o voo é reservado, comprado, e concluído, e a atividade é confirmada em compensável. Para confirmar CompensableActivity, use a atividade de Confirm e especificar CompensationToken de CompensableActivity para confirmar.

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
        }
    }
};

Este exemplo é o fluxo de trabalho em 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 o fluxo de trabalho é chamado, a saída a seguir são exibidas no console.

ReserveFlight: tíquete reservado.ManagerApproval: Aprovação do gerente recebida.PurchaseFlight: tíquete comprado.TakeFlight: voo concluído.ConfirmFlight: Voo tomado, nenhuma compensação possível.Fluxo de trabalho concluído com êxito com o status: Fechado.

Atividades de compensação de aninhamento

CompensableActivity pode ser colocado na seção de Body de outro CompensableActivity. CompensableActivity não pode ser colocado em um manipulador de outro CompensableActivity. É responsabilidade de um pai CompensableActivity garantir que quando é cancelado, confirmado, ou compensado, todas as atividades compensáveis filhos que eles concluíram com êxito e não foram confirmadas ou não foram compensadas deve ser compensado ou confirmadas antes que o pai conclua o cancelar, confirmação, ou a compensação. Se isso não é modelado explicitamente CompensableActivity pai compensará implicitamente atividades compensáveis filho se o pai recebeu o cancelamento ou compensa o sinal. Se o pai recebeu o sinal de confirmação o pai confirmará implicitamente atividades compensáveis filhos. Se a lógica para manipular cancelamento, confirmação, ou a compensação é modelada explicitamente no manipulador de CompensableActivitypai, qualquer filho tratado não explicitamente será confirmado implicitamente.

Confira também