Compartir a través de


Compensación

Este tema es aplicable a Windows Workflow Foundation 4.

La compensación en Windows Workflow Foundation (WF) es el mecanismo por el que se puede deshacer o compensar el trabajo previamente finalizado (siguiendo la lógica definida por la aplicación) cuando se produce un error posterior. Esta sección describe cómo usar la compensación en los flujos de trabajo.

Compensación en comparación con transacciones

Las transacciones permiten combinar varias operaciones en una sola unidad de trabajo. El uso de una transacción le da a la aplicación la posibilidad de anular (revertir) todos los cambios ejecutados desde dentro de la transacción si se produce algún error en cualquier momento del proceso de transacción. Sin embargo, el uso de las transacciones puede que no sea adecuado si el trabajo se está ejecutando mucho tiempo. Por ejemplo, se ha implementado una aplicación de planificación de viajes como un flujo de trabajo. Los pasos del flujo de trabajo pueden consistir en la reserva de un vuelo, la espera de la aprobación del administrador y, a continuación, el pago del vuelo. Este proceso podría tardar muchos días y no es práctico que los pasos de reservar y pagar el vuelo formen parte de la misma transacción. En un escenario como este, se podría usar la compensación para deshacer el paso de la reserva del flujo de trabajo si se produce un error posterior durante el procesamiento.

Usar CompensableActivity

CompensableActivity es la actividad de compensación principal en WF. Cualquier actividad que realice un trabajo que cabe la posibilidad de que deba compensarse se coloca en la propiedad Body de la clase CompensableActivity. En este ejemplo, el paso de la reserva de compra de un vuelo se encuentra en la propiedad Body de una clase CompensableActivity y la cancelación de la reserva está en la propiedad CompensationHandler. Justo después de CompensableActivity en el flujo de trabajo hay dos actividades que esperan la aprobación del administrador y, a continuación, se completa la compra del vuelo. Si una condición de error provoca la cancelación del flujo de trabajo después de que la clase CompensableActivity se haya completado correctamente, las actividades del controlador CompensationHandler se programan y el vuelo se cancela.

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

El siguiente ejemplo es el flujo de trabajo en XAML.

<Sequence
   xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="https://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>

Cuando se invoca el flujo de trabajo, en la consola se muestra el siguiente resultado.

ReserveFlight: Ticket is reserved. 
ManagerApproval: Manager approval received.
PurchaseFlight: Ticket is purchased.
Workflow completed successfully with status: Closed.
Dd489432.note(es-es,VS.100).gifNota:
Las actividades de ejemplo de este tema como ReserveFlight muestran su nombre y propósito a la consola para ayudar a mostrar el orden en el que se ejecutan las actividades cuando se produce la compensación.

Compensación predeterminada del flujo de trabajo

De forma predeterminada, si se cancela el flujo de trabajo, se ejecuta la lógica de compensación para cualquier actividad compensable que se haya completado y que aún no se haya confirmado o compensado.

Dd489432.note(es-es,VS.100).gifNota:
Cuando una clase CompensableActivity se confirma, ya no se podrá invocar la compensación para la actividad. El proceso de confirmación se describe más adelante en esta sección.

En este ejemplo, se inicia una excepción después de que se haya reservado el vuelo, aunque antes de que lo apruebe el administrador.

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

Este ejemplo es el flujo de trabajo en XAML.

<Sequence
   xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="https://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();

Cuando se invoca el flujo de trabajo, la aplicación host administra la excepción de condiciones de error simulada en OnUnhandledException, el flujo de trabajo se cancela y se invoca la lógica de compensación.

ReserveFlight: Ticket is reserved. 
SimulatedErrorCondition: Throwing an ApplicationException.
Workflow Unhandled Exception:
System.ApplicationException: Simulated error condition in the workflow.
CancelFlight: Ticket is canceled.
Workflow completed successfully with status: Canceled.

Cancelación y CompensableActivity

Si las actividades de la propiedad Body de una clase CompensableActivity no se han completado y la actividad se cancela, se ejecutan las actividades de la propiedad CancellationHandler.

Dd489432.note(es-es,VS.100).gifNota:
La propiedad CancellationHandler solo se invoca si las actividades de la propiedad Body de la clase CompensableActivity no se han completado y se cancela la actividad. La propiedad CompensationHandler solo se ejecuta si las actividades de la propiedad Body de la clase CompensableActivity se han completado correctamente y por lo tanto se invoca la compensación en la actividad.

La propiedad CancellationHandler permite a los autores del flujo de trabajo proporcionar una lógica de cancelación adecuada. En el siguiente ejemplo se produce una excepción durante la ejecución de la clase Body y, a continuación, se invoca la propiedad CancellationHandler.

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

Este ejemplo es el flujo de trabajo en XAML.

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

Cuando se invoca el flujo de trabajo, la aplicación host administra la excepción de condiciones de error simulada en OnUnhandledException, el flujo de trabajo se cancela y se invoca la lógica de cancelación de la clase CompensableActivity. En este ejemplo, la lógica de compensación y la lógica de cancelación tienen el mismo objetivo, cancelar el vuelo reservado.

ReserveFlight: Ticket is reserved. 
SimulatedErrorCondition: Throwing an ApplicationException.
Workflow Unhandled Exception:
System.ApplicationException: Simulated error condition in the workflow.
CancelFlight: Ticket is canceled.
Workflow completed successfully with status: Canceled.

Para obtener más información sobre la cancelación, vea Modelar el comportamiento de la cancelación en los flujos de trabajo.

Compensación explícita usando la actividad Compensate

En la sección anterior, se describió la compensación implícita. La compensación implícita puede ser adecuada para escenarios simples, pero si es necesario un mayor control sobre la programación de la administración de la compensación, se podrá usar la actividad Compensate. Para iniciar el proceso de compensación con la actividad Compensate, se usa la clase CompensationToken de la clase CompensableActivity para la que se desea la compensación. Se puede usar la actividad Compensate para iniciar la compensación en cualquier CompensableActivity completada que no se haya confirmado o compensado. Por ejemplo, se puede usar una actividad Compensate en la sección Catches de una actividad TryCatch o cuando desee después de que CompensableActivity se haya completado. En este ejemplo, la actividad Compensate se usa en la propiedad Catches de una actividad TryCatch para invertir la acción 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 ejemplo es el flujo de trabajo en XAML.

<TryCatch
   xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:s="clr-namespace:System;assembly=mscorlib"
   xmlns:x="https://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>

Cuando se invoca el flujo de trabajo, en la consola se muestra el siguiente resultado.

ReserveFlight: Ticket is reserved. 
SimulatedErrorCondition: Throwing an ApplicationException.
CancelFlight: Ticket is canceled.
Workflow completed successfully with status: Closed.

Confirmar compensación

De forma predeterminada, las actividades pueden compensarse en cualquier momento después de que se hayan completado. Sin embargo, en algunos escenarios es posible que no convenga hacerlo. En el ejemplo anterior la compensación para reservar el vale fue cancelar la reserva. No obstante, una vez que se haya completado el vuelo, la compensación dejará de tener validez. Al confirmar la actividad compensable, se invoca la actividad especificada por la propiedad ConfirmationHandler. Un posible uso de ello es permitir cualquier recurso que sea necesario para realizar la compensación que se va a liberar. Una vez confirmada la actividad compensable, no es posible compensarla y, si se intentara, se iniciaría una excepción InvalidOperationException. Cuando un flujo de trabajo se completa, todas las actividades compensables no confirmadas y no compensadas que se hayan completado se confirmarán en el orden inverso de la finalización. En este ejemplo, el vuelo se reserva, se compra y se completa y, a continuación, se confirma la actividad compensable. Para confirmar CompensableActivity, use la actividad Confirm y especifique la clase CompensationToken de la clase 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 ejemplo es el flujo de trabajo en XAML.

<Sequence
   xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities"
   xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
   xmlns:x="https://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>

Cuando se invoca el flujo de trabajo, en la consola se muestra el siguiente resultado.

ReserveFlight: Ticket is reserved. 
ManagerApproval: Manager approval received.
PurchaseFlight: Ticket is purchased.
TakeFlight: Flight is completed.
ConfirmFlight: Flight has been taken, no compensation possible.
Workflow completed successfully with status: Closed.

Anidar las actividades de compensación

CompensableActivity se puede colocar en la sección Body de otra CompensableActivity. Cuando esto se produce, es responsabilidad de la clase CompensableActivity primaria administrar la compensación y la confirmación de la CompensableActivity secundaria de una manera adecuada al propósito del flujo de trabajo. Si no se invoca ninguna confirmación explícita o compensación, se confirma la clase CompensableActivity secundaria cuando se confirme la actividad primaria, aunque no se invoca la compensación para la actividad secundaria cuando se invoca para la primaria.

Vea también

Tareas

Ejemplo de actividad compensable

Referencia

CompensableActivity
Compensate
Confirm
CompensationToken

Otros recursos

Compensation Programming Model
Compensation