次の方法で共有


報酬

Windows Workflow Foundation (WF) の補正は、後続の障害が発生したときに、以前に完了した作業を元に戻すか、(アプリケーションで定義されたロジックに従って) 補正できるメカニズムです。 このセクションでは、ワークフローで補正を使用する方法について説明します。

補正とトランザクション

トランザクションを使用すると、複数の操作を 1 つの作業単位に結合できます。 トランザクションを使用すると、トランザクション プロセスの一部でエラーが発生した場合に、トランザクション内から実行されたすべての変更を中止 (ロールバック) できます。 ただし、処理の実行時間が長い場合は、トランザクションの使用が適切でない場合があります。 たとえば、旅行計画アプリケーションはワークフローとして実装されます。 ワークフローの手順は、フライトの予約、マネージャーの承認の待機、フライトの支払いで構成される場合があります。 このプロセスには何日もかかる場合があり、フライトを予約して支払う手順が同じトランザクションに参加するのは実用的ではありません。 このようなシナリオでは、後で処理でエラーが発生した場合に、補正を使用してワークフローの予約手順を元に戻すことができます。

このトピックでは、ワークフローの補正について説明します。 ワークフロー内のトランザクションの詳細については、「 トランザクションTransactionScope」を参照してください。 トランザクションの詳細については、「 System.TransactionsSystem.Transactions.Transaction」を参照してください。

CompensableActivity の使用

CompensableActivity は WF のコア補正アクティビティです。 補正が必要な作業を実行するすべてのアクティビティは、CompensableActivityBodyに配置されます。 この例では、フライトを購入する予約手順がCompensableActivityBodyに配置され、予約のキャンセルがCompensationHandlerに配置されます。 ワークフローの CompensableActivity の直後には、マネージャーの承認を待ってからフライトの購入手順を完了する 2 つのアクティビティがあります。 エラー条件により、 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>

このワークフローが呼び出されると、次の出力がコンソールに表示されます。

予約フライト: チケットは予約済みです。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のホスト アプリケーションによって処理され、ワークフローが取り消され、補正ロジックが呼び出されます。

予約フライト: チケットは予約済みです。SimulatedErrorCondition: ApplicationException のスロー。ワークフローのハンドルされない例外:System.ApplicationException: ワークフロー内のシミュレートされたエラー条件。CancelFlight: チケットは取り消されます。ワークフローが正常に完了し、状態が [キャンセル済み] に設定されました。

Cancellation と CompensableActivity

CompensableActivityBody内のアクティビティが完了せず、アクティビティが取り消された場合、CancellationHandler内のアクティビティが実行されます。

CancellationHandlerは、CompensableActivityBody内のアクティビティが完了せず、アクティビティが取り消された場合にのみ呼び出されます。 CompensationHandlerは、CompensableActivityBody内のアクティビティが正常に完了し、その後アクティビティに対して補正が呼び出された場合にのみ実行されます。

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 はクレジット カードの料金を取り消しますが、 ReserveFlightBodyの最後のアクティビティであるため、フライトのキャンセルは試行されません。 ReserveFlightBodyの最後のアクティビティであるため、正常に完了した場合、Bodyは完了し、取り消しは不可能になります。

ChargeCreditCard: フライトのクレジット カードを請求します。SimulatedErrorCondition: ApplicationException のスロー。ワークフローのハンドルされない例外:System.ApplicationException: ワークフロー内のシミュレートされたエラー条件。CancelCreditCard: クレジット カードの料金を取り消します。ワークフローが正常に完了し、状態が [キャンセル済み] に設定されました。り消しの詳細については、「キャンセル」を参照してください。

補正アクティビティを使用した明示的な補正

前のセクションでは、暗黙的な補正について説明しました。 暗黙的な補正は単純なシナリオに適していますが、補正処理のスケジュールをより明示的に制御する必要がある場合は、 Compensate アクティビティを使用できます。 Compensate アクティビティを使用して補正プロセスを開始するには、補正が必要なCompensableActivityCompensationTokenが使用されます。 Compensate アクティビティを使用して、確認または補正されていない完了したCompensableActivityに対して補正を開始できます。 たとえば、Compensate アクティビティは、TryCatch アクティビティの Catches セクションで、またはCompensableActivityが完了した後いつでも使用できます。 この例では、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="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>

このワークフローが呼び出されると、次の出力がコンソールに表示されます。

予約フライト: チケットは予約済みです。SimulatedErrorCondition: ApplicationException のスロー。CancelFlight: チケットは取り消されます。ワークフローが正常に完了し、状態: [終了] が表示されました。

補正の確認

既定では、コンペンズ可能なアクティビティは、完了後いつでも補正できます。 一部のシナリオでは、これは適切でない場合があります。 前の例では、チケットを予約するための補償は予約をキャンセルすることでした。 ただし、フライトが完了すると、この補正手順は無効になります。 互換性のあるアクティビティを確認すると、 ConfirmationHandlerで指定されたアクティビティが呼び出されます。 これに使用できる 1 つの方法は、補正を実行するために必要なすべてのリソースを解放できるようにすることです。 コンペンズ可能なアクティビティが確認された後は、そのアクティビティを補正することはできず、これを試みると、 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="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>

このワークフローが呼び出されると、次の出力がコンソールに表示されます。

予約フライト: チケットは予約済みです。ManagerApproval: マネージャーの承認を受け取りました。PurchaseFlight: チケットが購入されました。TakeFlight: フライトが完了しました。ConfirmFlight: フライトは取得済みで、補償はできません。ワークフローが正常に完了し、状態: [終了] が表示されました。

補正アクティビティの入れ子

CompensableActivityは、別のCompensableActivityBody セクションに配置できます。 CompensableActivityを別のCompensableActivityのハンドラーに配置することはできません。 親の CompensableActivity は、取り消し、確認、または補償が行われたときに、正常に完了し、まだ確認または補償されていないすべての子依存アクティビティを、親が取り消し、確認、または補償を完了する前に確認または補償する必要があることを確認する必要があります。 これが明示的にモデル化されていない場合、親 CompensableActivity は、親がキャンセルまたは補正シグナルを受信した場合、子の依存可能なアクティビティを暗黙的に補正します。 親が確認シグナルを受け取った場合、親は子の依存可能なアクティビティを暗黙的に確認します。 キャンセル、確認、または補正を処理するロジックが親 CompensableActivityのハンドラーで明示的にモデル化されている場合、明示的に処理されない子は暗黙的に確認されます。

こちらも参照ください