補正

Windows Workflow Foundation (WF) の補正は、エラーが発生した場合に、前に完了した作業を (アプリケーションで定義されるロジックに応じて) 取り消したり補正したりできる機構です。 ここでは、ワークフローで補正を使用する方法について説明します。

補正とトランザクション

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

注意

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

CompensableActivity の使用

CompensableActivity は WF の中心的な補正アクティビティです。 補正が必要な可能性がある作業を実行するアクティビティは、すべて BodyCompensableActivity に配置されます。 この例では、航空券を購入する予約手順を BodyCompensableActivity に配置し、予約の取り消しを 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>

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

ReserveFlight: チケットが予約されました。ManagerApproval: マネージャーの承認を受け取りました。 PurchaseFlight: チケットが購入されました。 ワークフローは Closed の状態で正常に完了しました。

Note

ReserveFlight などのこのトピックのサンプル アクティビティでは、コンソールにアクティビティの名前や目的が表示されるため、補正が行われるときのアクティビティの順序がわかりやすくなっています。

既定のワークフローの補正

既定では、ワークフローを取り消すと、正常に完了済みであるがまだ確認または補正されていない補正可能なアクティビティに対して、補正ロジックが実行されます。

Note

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:\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: チケットが取り消されました。 ワークフローは Canceled の状態で正常に完了しました。

取り消しと CompensableActivity

BodyCompensableActivity 内のアクティビティが完了せず、アクティビティが取り消されると、CancellationHandler が実行されます。

Note

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

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: クレジット カードへの課金を取り消します。ワークフローは Canceled の状態で正常に完了しました。取り消し処理の詳細については、取り消しに関するページを参照してください。

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: チケットが取り消されました。 ワークフローは Closed の状態で正常に完了しました。

補正の確認

既定で、補正可能なアクティビティは、完了後の任意のタイミングで補正できます。 ただし、シナリオによっては適切でない場合があります。 前の例では、航空券予約の補正は、予約の取り消しでした。 ただし、この航空券に関する処理が完了すると、この補正手順は有効ではなくなります。 補正可能なアクティビティを確認すると、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: 航空券の購入処理が完了し、補正が行われる可能性はありません。 ワークフローは Closed の状態で正常に完了しました。

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

CompensableActivity は、別の BodyCompensableActivity セクションに配置できます。 CompensableActivity は、別の CompensableActivity のハンドラーで処理されない場合があります。 親の CompensableActivity が取り消されたとき、確認されたとき、または補正されたとき、正常に完了していてまだ確認または補正されていないすべての子の補正可能なアクティビティを、親が取り消し、確認、または補正を完了する前に、確認または補正されるようにするのが親のそのアクティビティの役割ですです。 補正が明示的にモデル化されていない場合、親の CompensableActivity は、親が取り消しまたは補正のシグナルを受け取ったとき、子の補正可能なアクティビティを暗黙的に補正します。 親は、確認通知を受け取ると、暗黙的に子の補正可能なアクティビティを確認します。 取り消し、確認、または補正を処理するロジックが親の CompensableActivity のハンドラーで明示的にモデル化されている場合、明示的に処理されなかった子は暗黙的に確認されます。

関連項目