Partilhar via


Compensação

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

Compensação vs. Transações

Uma transação permite combinar várias operações em uma única unidade de trabalho. O uso de uma transação dá ao seu aplicativo a capacidade de anular (reverter) todas as alterações executadas a partir da transação se ocorrerem erros durante qualquer parte do processo de transação. No entanto, o uso de transações pode não ser apropriado se o trabalho for de longa duração. Por exemplo, um aplicativo de planejamento de viagens é implementado como um fluxo de trabalho. As etapas do fluxo de trabalho podem consistir em reservar um voo, aguardar a aprovação do gerente e, em seguida, pagar pelo voo. Este processo pode demorar muitos dias e não é prático que as etapas de reserva e pagamento do voo participem na mesma transação. Em um cenário como este, a compensação pode ser usada para desfazer a etapa de reserva do fluxo de trabalho se houver uma falha posteriormente no processamento.

Nota

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

Usando CompensableActivity

CompensableActivity é a principal atividade de compensação em WF. Quaisquer atividades que executem trabalho que possa precisar ser compensado são colocadas no Body de um CompensableActivity. Neste exemplo, a etapa de reserva da compra de um voo é colocada no Body de a CompensableActivity e o cancelamento da reserva é colocado no CompensationHandler. Imediatamente após o CompensableActivity fluxo de trabalho são duas atividades que aguardam a aprovação do gerente e, em seguida, concluem a etapa de compra do voo. Se uma condição de erro fizer com que o fluxo de trabalho seja cancelado após a CompensableActivity conclusão bem-sucedida, as atividades no CompensationHandler manipulador serão agendadas e o voo será 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 é invocado, a saída a seguir é exibida no console.

ReserveFlight: O bilhete é reservado.ManagerApproval: aprovação do gerente recebida.PurchaseFlight: O bilhete é comprado.Fluxo de trabalho concluído com êxito com status: Fechado.

Nota

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

Compensação de fluxo de trabalho padrão

Por padrão, se o fluxo de trabalho for cancelado, a lógica de compensação será executada para qualquer atividade compensável que tenha sido concluída com êxito e ainda não tenha sido confirmada ou compensada.

Nota

Quando a CompensableActivity é confirmada, a compensação pela atividade não pode mais ser invocada. O processo de confirmação é descrito mais adiante nesta seção.

Neste exemplo, uma exceção é lançada depois que o voo é reservado, mas antes da etapa de aprovação do gerente.

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 é invocado, a exceção de condição de erro simulada é manipulada pelo aplicativo host no OnUnhandledException, o fluxo de trabalho é cancelado e a lógica de compensação é invocada.

ReserveFlight: O bilhete é reservado.SimulatedErrorCondition: lançando um ApplicationException.Workflow Unhandled Exception:System.ApplicationException: Condição de erro simulada no fluxo de trabalho.CancelFlight: O bilhete é cancelado.Fluxo de trabalho concluído com êxito com status: Cancelado.

Cancelamento e Atividade Compensatória

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

Nota

O CancellationHandler só é invocado se as atividades no Body do CompensableActivity não foram concluídas e a atividade é cancelada. O CompensationHandler só é executado se as atividades no Body âmbito do tiverem sido concluídas com sucesso e a CompensableActivity compensação for posteriormente invocada sobre a atividade.

O CancellationHandler fluxo de trabalho dá aos autores a oportunidade de fornecer qualquer lógica de cancelamento apropriada. No exemplo a seguir, uma exceção é lançada durante a execução do e, em Bodyseguida, o CancellationHandler é invocado.

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 é invocado, a exceção de condição de erro simulada é manipulada pelo aplicativo host em OnUnhandledException, o fluxo de trabalho é cancelado e a lógica de cancelamento do CompensableActivity é invocada. Neste exemplo, a lógica de compensação e a lógica de cancelamento têm objetivos diferentes. Se o Body concluído com sucesso, isso significa que o cartão de crédito foi cobrado e o voo reservado, então a compensação deve desfazer ambas as etapas. (Neste exemplo, cancelar o voo cancela automaticamente as cobranças do cartão de crédito.) No entanto, se o CompensableActivity for cancelado, isso significa que o Body não foi concluído e, portanto, a lógica do precisa ser capaz de CancellationHandler determinar como lidar melhor com o cancelamento. Neste exemplo, o CancellationHandler cancela a cobrança do cartão de crédito, mas como ReserveFlight foi a última atividade no Body, ele não tenta cancelar o voo. Uma vez que ReserveFlight foi a última atividade no Body, se tivesse sido concluída com sucesso, então o Body teria sido concluído e nenhum cancelamento seria possível.

ChargeCreditCard: Cobrar cartão de crédito para o voo.SimulatedErrorCondition: lançando um ApplicationException.Workflow Unhandled Exception:System.ApplicationException: Condição de erro simulada no fluxo de trabalho.CancelCreditCard: Cancele cobranças de cartão de crédito.Fluxo de trabalho concluído com êxito com status: Cancelado. Para obter mais informações sobre cancelamento, consulte Cancelamento.

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

Na secção anterior, a indemnização implícita foi abrangida. A compensação implícita pode ser apropriada para cenários simples, mas se for necessário um controle mais explícito sobre o agendamento do tratamento da compensação, a Compensate atividade pode ser usada. Para iniciar o processo de compensação com a Compensate atividade, utiliza-se a CompensationToken compensação para a CompensableActivity qual a compensação é desejada. A Compensate atividade pode ser usada para iniciar a compensação em qualquer conclusão CompensableActivity que não tenha sido confirmada ou compensada. Por exemplo, uma Compensate atividade pode ser usada na Catches seção de uma TryCatch atividade ou a qualquer momento após a CompensableActivity conclusão. Neste exemplo, a Compensate atividade é usada na Catches seção de uma TryCatch atividade para reverter a ação do 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 é invocado, a saída a seguir é exibida no console.

ReserveFlight: O bilhete é reservado.SimulatedErrorCondition: lançando um ApplicationException.CancelFlight: O bilhete é cancelado.Fluxo de trabalho concluído com êxito com status: Fechado.

Confirmação da Compensação

Por defeito, as atividades compensáveis podem ser compensadas a qualquer momento após a sua conclusão. Em alguns cenários, isso pode não ser apropriado. No exemplo anterior, a compensação para reservar o bilhete era cancelar a reserva. No entanto, após a conclusão do voo, esta etapa de compensação deixa de ser válida. A confirmação da atividade compensável invoca a atividade especificada pelo ConfirmationHandler. Um uso possível para isso é permitir que todos os recursos necessários para realizar a compensação sejam liberados. Depois que uma atividade compensável é confirmada, não é possível que ela seja compensada e, se isso for tentado, uma InvalidOperationException exceção é lançada. Quando um fluxo de trabalho é concluído com êxito, todas as atividades compensáveis não confirmadas e não compensadas concluídas com êxito são confirmadas na ordem inversa de conclusão. Neste exemplo, o voo é reservado, comprado e concluído e, em seguida, a atividade compensável é confirmada. Para confirmar um CompensableActivity, use a Confirm atividade e especifique o CompensationTokenCompensableActivity do 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 é invocado, a saída a seguir é exibida no console.

ReserveFlight: O bilhete é reservado.ManagerApproval: aprovação do gerente recebida.PurchaseFlight: O bilhete é comprado.TakeFlight: O voo está concluído.ConfirmFlight: O voo foi realizado, sem possibilidade de compensação.Fluxo de trabalho concluído com êxito com status: Fechado.

Atividades de compensação de aninhamento

A CompensableActivity pode ser colocado na Body seção de outro CompensableActivity. A CompensableActivity não pode ser colocado em um manipulador de outro CompensableActivity. É responsabilidade de um pai CompensableActivity garantir que, quando for cancelado, confirmado ou compensado, todas as atividades compensáveis da criança que tenham sido concluídas com sucesso e ainda não tenham sido confirmadas ou compensadas devem ser confirmadas ou compensadas antes que o pai complete o cancelamento, confirmação ou compensação. Se isso não for modelado explicitamente, o pai CompensableActivity compensará implicitamente as atividades compensáveis da criança se o pai recebeu o sinal de cancelamento ou compensação. Se o pai recebeu o sinal de confirmação, o pai confirmará implicitamente as atividades compensáveis da criança. Se a lógica para lidar com cancelamento, confirmação ou compensação for explicitamente modelada no manipulador do pai CompensableActivity, qualquer criança não tratada explicitamente será implicitamente confirmada.

Consulte também