Share via


补偿

本主题适用于 Windows Workflow Foundation 4。

Windows Workflow Foundation (WF) 中的补偿是一种机制,发生后续失败时,可以使用补偿(按照应用程序定义的逻辑)来撤消或补偿先前完成的工作。本节介绍如何在工作流中使用补偿。

补偿与事务

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

使用 CompensableActivity

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

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

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

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

ReserveFlight:预订机票。
ManagerApproval:获得经理批准。
PurchaseFlight:购买机票。
工作流成功完成,状态为“已关闭”。
Dd489432.note(zh-cn,VS.100).gif注意:
本主题中的示例活动(例如 ReserveFlight)显示将其名称和用途显示到控制台,以帮助说明在发生补偿时执行这些活动的顺序。

默认的工作流补偿

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

Dd489432.note(zh-cn,VS.100).gif注意:
CompensableActivity 为已确认时,不再为活动调用补偿。本节的后面部分介绍确认过程。

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

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

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

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

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

取消和 CompensableActivity

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

Dd489432.note(zh-cn,VS.100).gif注意:
如果 CompensableActivityBody 中的活动未完成并且取消了该活动,则只调用 CancellationHandler。如果 CompensableActivityBody 中的活动已成功完成并且随后对该活动调用了补偿,则只执行 CompensationHandler

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

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

当调用工作流时,将由 OnUnhandledException 中的宿主应用程序处理模拟错误条件异常,工作流被取消并调用 CompensableActivity 的取消逻辑。在此示例中,补偿逻辑和取消逻辑具有相同的目的,即,取消预订的机票。

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

有关取消的更多信息,请参阅为工作流中的取消行为进行建模

使用 Compensate 活动的显式补偿

在上一节中,我们介绍了隐式补偿。隐式补偿可能适合于简单方案,但如果在安排补偿处理时需要更多显式控件,则可以使用 Compensate 活动。若要使用 Compensate 活动启动补偿过程,请使用需要补偿的 CompensableActivityCompensationToken。可以使用 Compensate 活动在尚未确认或补偿的任何已完成的 CompensableActivity 上启动补偿。例如,可以在 TryCatch 活动的 Catches 部分中或完成 CompensableActivity 之后的任何时间使用 Compensate 活动。在此示例中,在 TryCatch 活动的 Catches 中使用了 Compensate 活动来反转 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="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>

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

ReserveFlight:预订机票。
SimulatedErrorCondition:引发 ApplicationException。
CancelFlight:取消机票。
工作流成功完成,状态为“已关闭”。

确认补偿

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

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="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>

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

ReserveFlight:预订机票。
ManagerApproval:获得经理批准。
PurchaseFlight:购买机票。
TakeFlight:乘坐航班。
ConfirmFlight:已乘坐航班,不再可能有补偿。
工作流成功完成,状态为“已关闭”。

嵌套补偿活动

可以将 CompensableActivity 放置到另一个 CompensableActivityBody 部分中。发生这种情况时,父 CompensableActivity 负责采用适合工作流用途的方式处理子 CompensableActivity 的补偿和确认。如果未调用任何显式确认或补偿,则当确认父活动时会确认子 CompensableActivity,但当在父活动上调用补偿时不会对子活动调用补偿。

另请参见

任务

可补偿活动示例

参考

CompensableActivity
Compensate
Confirm
CompensationToken

其他资源

Compensation Programming Model
Compensation