薪酬

Windows Workflow Foundation (WF) 中的補償機制提供了一種方法,當後續發生失敗時,可以根據應用程式定義的邏輯來復原或補償先前完成的工作。 本節說明如何在工作流程中使用補償。

補償與交易

交易可讓您將多個作業合併成單一工作單位。 使用交易可讓應用程式在交易程式的任何部分發生任何錯誤時,中止(回復)交易內執行的所有變更。 如果工作是長時間執行的,則使用交易可能不合適。 例如,旅遊規劃應用程式會實作為工作流程。 工作流程的步驟可能包括預約航班、等候經理核准,然後支付航班費用。 這個過程可能需要數天時間,對於預訂和支付航班納入同一筆交易來說並不實用。 在這類情況下,如果稍後處理失敗,可以利用補償來撤銷工作流程中的預約步驟。

備註

本主題涵蓋工作流程中的補償。 如需工作流程中交易的詳細資訊,請參閱 交易 和 TransactionScope。 如需交易的詳細資訊,請參閱 System.Transactions 和 System.Transactions.Transaction。

使用可補償活動

CompensableActivity 是 WF 的核心補償活動。 任何執行可能需要補償之工作的活動都會被納入 Body的 CompensableActivity 中。 在此範例中,預訂航班的步驟被放入 Body 的 CompensableActivity,而取消預訂則放入 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:票已預訂。模擬錯誤條件:拋出應用程式例外。工作流程未處理的例外:系統應用程式例外:工作流程中的模擬錯誤條件。CancelFlight:票已取消。工作流程以狀態“已取消”成功完成。

取消和可補償活動

如果 Body 的 CompensableActivity 活動尚未完成,而該活動被取消,則會執行 CancellationHandler 中的活動。

備註

只有當 CancellationHandler 中的 Body 活動尚未完成並且活動被取消時,才會被啟動 CompensableActivity。 只有當 CompensationHandler 中的 Body 活動順利完成後,並隨後在該活動上觸發補償時,才會執行 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的最後一個活動,因此不會嘗試取消航班。 由於 ReserveFlight 是 Body的最後一個活動,如果它已經成功完成,則 Body 已完成,而且不可能取消。

ChargeCreditCard:為航班收取信用卡費用。SimulatedErrorCondition:拋出 ApplicationException。工作流程未處理的例外狀況:System.ApplicationException:工作流程中的模擬錯誤條件。CancelCreditCard:取消信用卡收費。工作流程成功完成,狀態為:已取消。 如需取消的詳細資訊,請參閱 取消。

使用補償活動功能進行明確補償

在上一節中,涵蓋隱含補償。 隱含補償可以適用於簡單案例,但如果需要更明確的控制來排程補償處理,則可以使用 Compensate 活動。 若要使用 Compensate 活動啟動補償程序,需要使用所要補償之 CompensationToken 的 CompensableActivity。 這個 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 活動,並指定 CompensationToken 的 CompensableActivity 來進行確認。

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 可以被放在另一個 Body的 CompensableActivity 區域中。 CompensableActivity 不能被放入另一個 CompensableActivity 的處理程式中。 父母 CompensableActivity 的責任是確保取消、確認或補償后,所有順利完成、尚未確認或補償的兒童補償活動,必須在父母完成取消、確認或補償之前確認或補償。 如果未明確建立模型,當父系 CompensableActivity 收到取消或補償訊號時,將會自動補償子系可補償活動。 如果父系收到確認訊號,父代會隱含地確認子可補償活動。 如果處理取消、確認或補償的邏輯在父 CompensableActivity的處理程序中被明確定義,則任何未明確處理的子系都會被隱含地確認。

另請參閱