Compartir a través de


Compensación

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

Compensación frente a transacciones

Una transacción permite combinar varias operaciones en una sola unidad de trabajo. El uso de una transacción permite a la aplicación anular (revertir) todos los cambios ejecutados desde dentro de la transacción si se producen errores durante cualquier parte del proceso de transacción. Sin embargo, es posible que el uso de transacciones no sea adecuado si el trabajo es de larga duración. Por ejemplo, una aplicación de planificación de viajes se implementa como un flujo de trabajo. Los pasos del flujo de trabajo pueden constar de reservar un vuelo, esperar la aprobación del administrador y luego pagar el vuelo. Este proceso puede tardar muchos días y no es práctico para los pasos de reserva y pago del vuelo para participar en la misma transacción. En un escenario como este, se podría usar la compensación para deshacer el paso de reserva del flujo de trabajo si se produce un error más adelante en el procesamiento.

Nota:

En este tema se trata la compensación en flujos de trabajo. Para obtener más información sobre las transacciones en flujos de trabajo, vea Transacciones y TransactionScope. Para obtener más información sobre las transacciones, vea System.Transactions y System.Transactions.Transaction.

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 reserva de la compra de un vuelo se coloca en el Body de un CompensableActivity, y la cancelación de la reserva se coloca en el CompensationHandler. Inmediatamente después del CompensableActivity en el flujo de trabajo, hay dos actividades que esperan la aprobación del gerente y, a continuación, completan el paso de 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()
    }
};

En el ejemplo siguiente se muestra el flujo de trabajo en 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>

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

ReserveFlight: el billete está reservado.ManagerApproval: aprobación del administrador recibida.PurchaseFlight: se compra el vale.Flujo de trabajo completado correctamente con el estado: Cerrado.

Nota:

Las actividades de ejemplo que se mencionan en este tema, como ReserveFlight, muestran su nombre y propósito en la consola para ilustrar el orden de ejecución de las actividades cuando se produce una compensación.

Compensación de flujo de trabajo predeterminada

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

Nota:

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="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:
        {e.TerminationException.GetType().FullName}: {e.TerminationException.Message}
        """);
    }
    else
    {
        Console.WriteLine($"Workflow completed successfully with status: {e.CompletionState}.");
    }

    syncEvent.Set();
};

wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
    Console.WriteLine($"""
    Workflow Unhandled Exception:
    {e.UnhandledException.GetType().FullName}: {e.UnhandledException.Message}
    """);

    return UnhandledExceptionAction.Cancel;
};

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

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

ReserveFlight: se reserva el billete.SimulatedErrorCondition: inicia una ApplicationException.Excepción no controlada del flujo de trabajo:System.ApplicationException: condición de error simulada en el flujo de trabajo.CancelFlight: se cancela el billete.Flujo de trabajo completado correctamente con el estado: cancelado.

Cancelación y CompensableActivity

Si las actividades del Body de un CompensableActivity no se han completado y se cancela la actividad, se ejecutan las actividades del CancellationHandler.

Nota:

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.

CancellationHandler proporciona a los autores de flujos de trabajo la oportunidad de proporcionar cualquier lógica de cancelación adecuada. En el ejemplo siguiente, se produce una excepción durante la ejecución de Bodyy, a continuación, se invoca .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()
    }
};

Este ejemplo es el flujo de trabajo en 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>

Cuando se invoca el flujo de trabajo, la aplicación host maneja la excepción de la condición de error simulada en OnUnhandledException, se cancela el flujo de trabajo y se invoca la lógica de cancelación de CompensableActivity. En este ejemplo, la lógica de compensación y la lógica de cancelación tienen objetivos diferentes. Si Body se completó correctamente, significa que la tarjeta de crédito se cargó y el vuelo se reservó, por lo que la compensación debe deshacer ambos pasos. (En este ejemplo, cancelar el vuelo cancela automáticamente los cargos de la tarjeta de crédito). Sin embargo, si CompensableActivity se cancela, esto significa que Body no se completó y, por lo tanto, la lógica de CancellationHandler debe ser capaz de determinar cómo manejar mejor la cancelación. En este ejemplo, CancellationHandler cancela el cargo de la tarjeta de crédito, pero como ReserveFlight fue la última actividad en Body, no intenta cancelar el vuelo. Puesto que ReserveFlight fue la última actividad en Body, si se hubiera completado correctamente, entonces Body también se habría completado y no sería posible cancelar.

ChargeCreditCard: cargar el billete a la tarjeta de crédito.SimulatedErrorCondition: inicia una ApplicationException.Excepción no controlada del flujo de trabajo:System.ApplicationException: condición de error simulada en el flujo de trabajo.CancelCreditCard: cancelación de cargos de la tarjeta de crédito.Flujo de trabajo completado correctamente con el estado: Cancelado. Para más información sobre la cancelación, consulte Cancelación.

Compensación explícita usando la actividad Compensate

En la sección anterior, se ha cubierto la compensación implícita. La compensación implícita puede ser adecuada para escenarios simples, pero si se requiere un control más explícito sobre la programación del control de compensación, se puede usar la Compensate actividad. 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 utiliza en la sección Catches de una actividad TryCatch para revertir la acción de la 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="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>

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

ReserveFlight: se reserva el billete.SimulatedErrorCondition: inicia una ApplicationException.CancelFlight: se cancela el billete.Flujo de trabajo completado correctamente con el estado: cerrado.

Confirmación de la compensación

De forma predeterminada, las actividades compensables se pueden compensar en cualquier momento después de que se hayan completado. En algunos escenarios, es posible que esto no sea adecuado. En el ejemplo anterior, la compensación para reservar el billete era cancelar la reserva. Sin embargo, una vez completado el vuelo, este paso de compensación ya no es válido. Al confirmar la actividad compensable, se invoca la actividad especificada por .ConfirmationHandler Un posible uso para esto es permitir que se libere cualquier recurso necesario para realizar la compensación. Una vez confirmada una actividad compensable, no es posible compensarla y, si se intenta, se produce una InvalidOperationException excepción. Cuando un flujo de trabajo se completa correctamente, todas las actividades compensables no confirmadas y no compensadas que se completaron correctamente se confirman en orden inverso de finalización. En este ejemplo, el vuelo está reservado, comprado y completado 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="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>

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

ReserveFlight: el billete está reservado.ManagerApproval: aprobación del administrador recibida.PurchaseFlight: se compra el vale.TakeFlight: Se ha completado el vuelo.ConfirmFlight: El vuelo se ha tomado, no se puede compensar.Flujo de trabajo completado correctamente con el estado: Cerrado.

Anidar las actividades de compensación

Un CompensableActivity se puede colocar en la sección Body de otro CompensableActivity. No se puede colocar un CompensableActivity en un controlador de otro CompensableActivity. Es responsabilidad de un padre CompensableActivity asegurarse de que cuando se cancele, confirme o compense, todas las actividades compensables infantiles que se hayan completado correctamente y que aún no se hayan confirmado o compensado deben confirmarse o compensarse antes de que el padre complete la cancelación, confirmación o compensación. Si esto no se modeló explícitamente, la CompensableActivity primaria compensará implícitamente las actividades compensables secundarias si el elemento primario recibió la señal de cancelación o compensación. Si el padre recibió la señal de confirmación, el padre confirmará implícitamente las actividades compensables del hijo. Si la lógica para controlar la cancelación, confirmación o compensación se modela explícitamente en el controlador de la CompensableActivity primaria, cualquier elemento secundario no controlado explícitamente se confirmará implícitamente.

Consulte también