薪酬

Windows Workflow Foundation 中的补偿(WF)是一种机制,在发生后续故障时,可以撤消或补偿以前完成的工作(遵循应用程序定义的逻辑)。 本部分介绍如何在工作流中使用补偿。

补偿与交易

通过事务可以将多个操作合并为单个工作单元。 使用事务时,如果事务进程中任何部分出现错误,则你的应用程序可以中止(回滚)在事务内执行的所有更改。 但是,如果工作长时间运行,使用事务可能不合适。 例如,旅行规划应用程序作为工作流实现。 工作流的步骤可能包括预订航班、等待经理批准,然后支付航班费用。 此过程可能需要耗费多天,因此将预订和支付航班包含在同一交易中并不实际。 在此类方案中,如果在以后的处理中出现失败,可以使用补偿来撤消工作流的预订步骤。

注释

本主题介绍工作流中的补偿。 有关工作流中的事务的详细信息,请参阅 事务TransactionScope。 有关事务的详细信息,请参阅 System.TransactionsSystem.Transactions.Transaction

使用 CompensableActivity

CompensableActivity 是 WF 中的核心补偿活动。 将执行可能需要补偿的工作的所有活动放置到 BodyCompensableActivity 中。 在此示例中,购买航班的预订步骤被放置在 BodyCompensableActivity 中,而预订的取消步骤被放置在 CompensationHandler 中。 紧接着在工作流中的 CompensableActivity 之后是两个活动,即等待经理批准,然后完成购买机票的步骤。 如果在 CompensableActivity 成功完成之后某个错误条件导致要取消工作流,则会安排 CompensationHandler 处理程序中的活动并且取消相应的航班。

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

以下示例是 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>

在调用工作流时,将下面的输出显示到控制台。

ReserveFlight:保留票证。ManagerApproval:已收到经理批准。PurchaseFlight:购买票证。工作流成功完成,状态为:已关闭。

注释

本主题中的示例活动,例如 ReserveFlight 向控制台显示其名称和用途,以帮助说明在发生补偿时执行活动的顺序。

默认工作流补偿

默认情况下,如果取消了工作流,则会针对已成功且尚未确认或补偿的任何可补偿活动运行补偿逻辑。

注释

CompensableActivity 为已确认时,不再为活动调用补偿。 本部分稍后将介绍确认过程。

在本示例中,预订航班之后,但在经理批准步骤之前引发了一个异常。

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

此示例是 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();

调用工作流时,模拟的错误条件异常由主机应用程序 OnUnhandledException处理,工作流被取消,并调用补偿逻辑。

ReserveFlight:预订机票。SimulatedErrorCondition:引发 ApplicationException。工作流未处理的异常:System.ApplicationException:工作流中的模拟错误条件。CancelFlight:取消机票。工作流成功完成,状态为“已取消”。

取消和 CompensableActivity

如果 BodyCompensableActivity 中的活动未完成并且取消了该活动,则执行 CancellationHandler 中的活动。

注释

只有当CancellationHandler中的Body活动尚未完成且活动被取消时,才会调用CompensableActivity。 如果 CompensationHandlerBody 中的活动已成功完成并且随后对该活动调用了补偿,则只执行 CompensableActivity

CancellationHandler 为工作流作者创造了机会来提供任何适当的取消逻辑。 在以下示例中,Body 执行期间会引发异常,然后调用 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()
    }
};

此示例是 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>

调用工作流时,OnUnhandledException 中的主机应用程序处理了模拟的错误条件异常,工作流被取消,并调用了 CompensableActivity 的取消逻辑。 在此示例中,补偿逻辑和取消逻辑有不同的目标。 Body如果成功完成,则表示信用卡已收取费用,航班已预订,因此赔偿应撤消这两个步骤。 (在此示例中,取消航班会自动取消信用卡费用。)但是,如果 CompensableActivity 取消,这意味着 Body 未完成,因此 CancellationHandler 的逻辑需要能够确定如何最好地处理取消。 在此示例中,CancellationHandler 取消了这笔信用卡费用,但由于 ReserveFlight 是在 Body 中的最后一个活动,因此它不会尝试取消航班。 ReserveFlightBody中的最后一个活动,如果它成功完成,那么Body也会完成,因此无法取消。

ChargeCreditCard:收取信用卡费用以支付航班。SimulatedErrorCondition:抛出 ApplicationException。工作流未经处理的异常:System.ApplicationException:工作流中的模拟错误条件。CancelCreditCard:取消信用卡扣费。工作流已成功完成,状态为:已取消。 有关取消的详细信息,请参阅 “取消”。

使用 Compensate 活动的显式补偿

在上一部分中,已涵盖隐式补偿。 隐式补偿适用于简单场景,但如果需要对补偿处理的调度进行更明确的控制,则可以使用Compensate活动。 若要使用 Compensate 活动启动补偿过程,请使用需要补偿的 CompensationTokenCompensableActivity。 可以使用 Compensate 活动在尚未确认或补偿的任何已完成的 CompensableActivity 上启动补偿。 例如,Compensate活动可以在Catches活动的TryCatch部分使用,也可以在CompensableActivity活动完成后的任何时间使用。 在本示例中,在 Compensate 活动的 Catches 部分中使用了 TryCatch 活动以反转 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
                }
            }
        }
    }
};

此示例是 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>

在调用工作流时,将下面的输出显示到控制台。

ReserveFlight:票证已预订。SimulatedErrorCondition:抛出 ApplicationException。CancelFlight:票证已取消。工作流成功完成,状态为:已关闭。

确认补偿

默认情况下,可补偿活动可以在完成后随时进行补偿。 在某些情况下,这可能不合适。 在上一个示例中,对预订机票的补偿是取消预订。 但是,在完成航班后,此补偿步骤不再有效。 确认可补偿活动会调用ConfirmationHandler指定的活动。 一种可能的用途是允许释放执行补偿所需的任何资源。 在确认可补偿的活动之后,不能再对该活动进行补偿,并且如果尝试该操作,则会引发一个 InvalidOperationException 异常。 当工作流成功完成时,所有已成功完成但未确认和未补偿的可补偿活动将按完成顺序的逆序进行确认。 在本示例中,预订航班、购买机票并完成,然后确认可补偿的活动。 若要确认某个 CompensableActivity,请使用 Confirm 活动并指定要确认的 CompensationTokenCompensableActivity

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

此示例是 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>

在调用工作流时,将下面的输出显示到控制台。

ReserveFlight:机票已保留。ManagerApproval:已收到经理批准。PurchaseFlight:机票已购买。TakeFlight:航班已完成。ConfirmFlight:航班已起飞,无法进行赔偿。工作流成功完成,状态为:已关闭。

嵌套补偿活动

可以将 CompensableActivity 放入另一个 BodyCompensableActivity 部分。 不能将 A CompensableActivity 置于另一个 CompensableActivity处理程序中。 在父级完成取消、确认或补偿操作前,确保在 CompensableActivity 父级取消、确认或补偿时,所有已成功完成的和尚未得到确认或补偿的子级可补偿活动必须得到确认和补偿是该父级的责任。 如果不对此进行显式建模,在该父级接收到取消或补偿信号时,CompensableActivity 将隐式补偿可补偿的子级活动。 如果父级收到了确认信号,则该父级将隐式确认可补偿的子级活动。 如果用于处理取消、确认或补偿的逻辑在父 CompensableActivity 的处理程序中被显式建模,则未显式处理的所有子级都将被隐式确定。

另请参阅