次の方法で共有


補正

このトピックの内容は、Windows Workflow Foundation 4 に該当します。

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

補正とトランザクション

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

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="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: チケットが購入されました。
ワークフローは Closed の状態で正常に完了しました。
Dd489432.note(ja-jp,VS.100).gif注 :
ReserveFlight などのこのトピックのサンプル アクティビティでは、コンソールにアクティビティの名前や目的が表示されるため、補正が行われるときのアクティビティの順序がわかりやすくなっています。

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

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

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

取り消しと CompensableActivity

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

Dd489432.note(ja-jp,VS.100).gif注 :
CancellationHandler が呼び出されるのは、CompensableActivityBody 内のアクティビティが完了せず、アクティビティが取り消された場合だけです。CompensationHandler は、CompensableActivityBody 内のアクティビティが正常に完了し、その後にアクティビティで補正が呼び出された場合にのみ実行されます。

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

取り消し詳細情報、「ワークフローでの取り消し動作のモデル化」を参照してください。

Compensate アクティビティを使用する明示的な補正

前のセクションでは、暗黙的な補正について説明しました。暗黙的な補正は単純なシナリオには適していますが、補正処理のスケジュールに関して、より明示的な制御が必要な場合は、Compensate アクティビティを使用できます。Compensate アクティビティを使用して補正プロセスを開始するには、補正が望ましい CompensableActivityCompensationToken を使用します。Compensate アクティビティは、完了した CompensableActivity で、まだ確認または補正されていない場合に補正を開始するときに使用できます。たとえば、Compensate アクティビティは TryCatch アクティビティの Catches セクションで使用できます。また、CompensableActivity が完了した後の任意のタイミングで使用できます。この例では、Compensate アクティビティを TryCatch アクティビティの Catches に使用し、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: チケットは取り消されました。
ワークフローは Closed の状態で正常に完了しました。

補正の確認

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

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

CompensableActivity は、別の CompensableActivityBody セクションに配置できます。この処理が発生したときに、ワークフローの目的に適した方法で、子の CompensableActivity の補正と確認を処理するのは、親の CompensableActivity の役割です。明示的な確認または補正が呼び出されない場合、親アクティビティが確認されたときに子の CompensableActivity が確認されます。ただし、親アクティビティで補正が呼び出されても、子アクティビティで補正は呼び出されません。

参照

処理手順

補正可能なアクティビティのサンプル

リファレンス

CompensableActivity
Compensate
Confirm
CompensationToken

その他のリソース

Compensation Programming Model
Compensation