ワークフローでの取り消し動作のモデル化

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

アクティビティは、たとえば Parallel アクティビティを使用して、ワークフロー内部で取り消すことができます。このアクティビティでは、CompletionCondition の評価結果が true になると、完了していない分岐が取り消されます。また、ホストで Cancel を呼び出すと、アクティビティをワークフロー外から取り消すこともできます。取り消し処理を指定するには、ワークフローの作成者は、CancellationScope アクティビティまたは CompensableActivity アクティビティを使用するか、取り消しロジックを指定するカスタム アクティビティを作成します。このトピックでは、ワークフローにおける取り消しの概要について説明します。

取り消し、補正、およびトランザクション

アプリケーションでトランザクションを使用すると、トランザクションのプロセスでエラーが発生した場合、トランザクション内で実行されたすべての変更を中止 (ロールバック) できます。ただし、長期間にわたって実行される処理やトランザクション リソースを含まない処理など、取り消す (元に戻す) 必要がある処理によってはトランザクションに適さないものもあります。補正は、ワークフローでエラーが発生した場合に、前に完了した非トランザクションの処理を元に戻すためのモデルを提供します。取り消しは、完了していない非トランザクションの処理をワークフローやアクティビティの作成者が処理できるようにするためのモデルを提供します。実行を完了していないアクティビティが取り消された場合、取り消しロジックが指定されていればそのロジックが呼び出されます。

Ff407124.note(ja-jp,VS.100).gif注 :
トランザクションと補正詳細情報、「ワークフロー トランザクション」および「補正」を参照してください。

CancellationScope の使用

CancellationScope アクティビティには、子アクティビティを含めることができる Body および CancellationHandler という 2 つのセクションがあります。Body には、アクティビティのロジックを構成するアクティビティを配置し、CancellationHandler には、アクティビティの取り消しロジックを指定するアクティビティを配置します。アクティビティは、完了していない場合にのみ取り消すことができます。CancellationScope アクティビティの場合は、Body のアクティビティが完了すると完了したことになります。取り消し要求がスケジュールされている場合、Body のアクティビティが完了していなければ、CancellationScopeCanceled とマークされ、CancellationHandler アクティビティが実行されます。

ホストからのワークフローの取り消し

ホストでワークフローを取り消すには、ワークフローをホストしている WorkflowApplication インスタンスの Cancel メソッドを呼び出します。次の例では、CancellationScope を含むワークフローが作成されます。このワークフローが呼び出され、ホストで Cancel が呼び出されます。ワークフローのメインの実行が停止し、CancellationScopeCancellationHandler メソッドが呼び出され、ワークフローがステータス Canceled で完了します。

Activity wf = new CancellationScope
{
    Body = new Sequence
    {
        Activities = 
        {
            new WriteLine
            {
                Text = "Starting the workflow."
            },
            new Delay
            {
                Duration = TimeSpan.FromSeconds(5)
            },
            new WriteLine
            {
                Text = "Ending the workflow."
            }
        }
    },
    CancellationHandler = new WriteLine
    {
        Text = "CancellationHandler invoked."
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Subscribe to any desired workflow lifecycle events.
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

Thread.Sleep(TimeSpan.FromSeconds(1));

wfApp.Cancel();

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

ワークフローを開始します。
CancellationHandler が呼び出されました。
ワークフロー b30ebb30-df46-4d90-a211-e31c38d8db3c が取り消されました。
Ff407124.note(ja-jp,VS.100).gif注 :
CancellationScope アクティビティを取り消して CancellationHandler を呼び出す場合、適切な取り消しロジックを指定するには、ワークフローの作成者が、アクティビティが取り消されるまでの進行状況を確認する必要があります。CancellationHandler からは、取り消されるアクティビティの進行状況に関する情報は提供されません。

ワークフローのルートの後に未処理の例外がバブルアップされ、OnUnhandledException ハンドラーから Cancel が返された場合も、ホストからワークフローを取り消すことができます。次の例では、ワークフローの開始後に、ワークフローから ApplicationException がスローされます。この例外はワークフローで処理されないため、OnUnhandledException ハンドラーが呼び出されます。ハンドラーがランタイムにワークフローを取り消すように指示し、現在実行中の CancellationScope アクティビティの CancellationHandler が呼び出されます。

Activity wf = new CancellationScope
{
    Body = new Sequence
    {
        Activities = 
        {
            new WriteLine
            {
                Text = "Starting the workflow."
            },
            new Throw
            {
                 Exception = new InArgument<Exception>((env) => 
                     new ApplicationException("An ApplicationException was thrown."))
    
            },
            new WriteLine
            {
                Text = "Ending the workflow."
            }
        }
    },
    CancellationHandler = new WriteLine
    {
        Text = "CancellationHandler invoked."
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Subscribe to any desired workflow lifecycle events.
wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
    // Display the unhandled exception.
    Console.WriteLine("OnUnhandledException in Workflow {0}\n{1}",
        e.InstanceId, e.UnhandledException.Message);

    // Instruct the runtime to cancel the workflow.
    return UnhandledExceptionAction.Cancel;
};

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

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

ワークフローを開始します。
ワークフロー 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 の OnUnhandledException
ApplicationException がスローされました。
CancellationHandler が呼び出されました。
ワークフロー 6bb2d5d6-f49a-4c6d-a988-478afb86dbe9 が取り消されました。

ワークフロー内部からのアクティビティの取り消し

アクティビティは、親から取り消すこともできます。たとえば、Parallel アクティビティに実行中の分岐が複数ある場合、その CompletionCondition の評価結果が true になると、完了していない分岐は取り消されます。次の例では、2 つの分岐がある Parallel アクティビティが作成されます。CompletionConditiontrue に設定されているため、どちらかの分岐が完了した時点で Parallel が完了します。この例では、分岐 2 が完了するため、分岐 1 が取り消されます。

Activity wf = new Parallel
{
    CompletionCondition = true,
    Branches = 
    {
        new CancellationScope
        {
            Body = new Sequence
            {
                Activities = 
                {
                    new WriteLine
                    {
                        Text = "Branch 1 starting."
                    },
                    new Delay
                    {
                         Duration = TimeSpan.FromSeconds(2)
                    },
                    new WriteLine
                    {
                        Text = "Branch 1 complete."
                    }
                }
            },
            CancellationHandler = new WriteLine
            {
                Text = "Branch 1 canceled."
            }
        },
        new WriteLine
        {
            Text = "Branch 2 complete."
        }
    }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

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

分岐 1 を開始します。
分岐 2 が完了しました。
分岐 1 が取り消されました。
ワークフロー e0685e24-18ef-4a47-acf3-5c638732f3be が完了しました。

アクティビティは、アクティビティのルートの後に例外がバブルアップされ、その例外がワークフローの上位で処理されていない場合にも取り消されます。次の例では、ワークフローのメイン ロジックが Sequence アクティビティで構成されています。Sequence は、TryCatch アクティビティに含まれる CancellationScope アクティビティの Body として指定されています。Sequence の本体から例外がスローされて親の TryCatch アクティビティによって処理され、Sequence が取り消されます。

Activity wf = new TryCatch
{
    Try = new CancellationScope
    {
        Body = new Sequence
        {
            Activities = 
            {
                new WriteLine
                {
                    Text = "Sequence starting."
                },
                new Throw
                {
                     Exception = new InArgument<Exception>((env) => 
                         new ApplicationException("An ApplicationException was thrown."))
        
                },
                new WriteLine
                {
                    Text = "Sequence complete."
                }
            }
        },
        CancellationHandler = new WriteLine
        {
            Text = "Sequence canceled."
        }
    },
    Catches =
    {
        new Catch<ApplicationException>
        {
            Action = new ActivityAction<ApplicationException>
            {
                Handler  = new WriteLine
                {
                    Text = "Exception caught."
                }
            }
        }
    }

};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine("Workflow {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);
    }
};

// Run the workflow.
wfApp.Run();

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

シーケンスを開始します。
シーケンスが取り消されました。
例外がキャッチされました。
ワークフロー e3c18939-121e-4c43-af1c-ba1ce977ce55 が完了しました。

CancellationHandler からの例外のスロー

CancellationScopeCancellationHandler からスローされるすべての例外は、ワークフローにとって致命的なものです。例外が CancellationHandler からエスケープされる可能性がある場合は、CancellationHandlerTryCatch を使用して、それらの例外をキャッチして処理するようにしてください。

CompensableActivity を使用した取り消し

CancellationScope アクティビティと同様に、CompensableActivity には CancellationHandler があります。CompensableActivity が取り消されると、その CancellationHandler に含まれるアクティビティが呼び出されます。これは、部分的に完了した補正可能な処理を元に戻す場合に役立ちます。補正や取り消しに CompensableActivity を使用する方法については、「補正」を参照してください。

カスタム アクティビティを使用した取り消し

カスタム アクティビティの作成者は、いくつかの方法でカスタム アクティビティに取り消しロジックを実装することができます。Activity から派生するカスタム アクティビティに取り消しロジックを実装するには、アクティビティの本体に CancellationScope、または取り消しロジックを含むその他のカスタム アクティビティを配置します。AsyncCodeActivity 派生アクティビティと NativeActivity 派生アクティビティの場合はそれぞれ、Cancel メソッドをオーバーライドして取り消しロジックを指定できます。CodeActivity 派生アクティビティでは、取り消しを指定することはできません。ランタイムで Execute メソッドを呼び出すと、すべての処理が一度に実行されるからです。実行メソッドが呼び出される前に CodeActivity ベースのアクティビティを取り消した場合、アクティビティはステータス Canceled で終了し、Execute メソッドは呼び出されません。

NativeActivity を使用した取り消し

NativeActivity 派生アクティビティでは、Cancel メソッドをオーバーライドしてカスタムの取り消しロジックを指定できます。このメソッドがオーバーライドされていない場合は、既定のワークフロー取り消しロジックが適用されます。既定の取り消しは、NativeActivityCancel メソッドがオーバーライドされていない場合、つまり Cancel メソッドが基本の NativeActivity Cancel メソッドを呼び出す場合に発生する処理です。アクティビティが取り消されると、ランタイムは、取り消し対象のフラグをアクティビティに設定し、特定のクリーンアップ処理を自動的に実行します。アクティビティに未処理のブックマークしかない場合、それらのブックマークが削除され、アクティビティが Canceled とマークされます。取り消されるアクティビティに未処理の子アクティビティがある場合は、それらも取り消されます。子アクティビティを追加でスケジュールしようとすると無視され、アクティビティは Canceled とマークされます。未処理の子アクティビティが Canceled または Faulted の状態で完了した場合、アクティビティは Canceled とマークされます。取り消し要求は無視される場合があることに注意してください。アクティビティに未処理のブックマークも実行中の子アクティビティもなく、取り消し対象のフラグが設定された後に追加の作業項目のスケジュールも行っていない場合は、アクティビティが正常に完了します。ほとんどの場合はこの既定の取り消しで十分ですが、別の取り消しロジックが必要な場合は、組み込みの取り消しアクティビティやカスタム アクティビティを使用できます。

次の例では、NativeActivity ベースのカスタム ParallelForEach アクティビティの Cancel オーバーライドが定義されます。アクティビティが取り消されると、このオーバーライドによってアクティビティの取り消しロジックが処理されます。この例は、「非ジェネリックの ParallelForEach」のサンプルの一部です。

protected override void Cancel(NativeActivityContext context)
{
    // If we do not have a completion condition then we can just
    // use default logic.
    if (this.CompletionCondition == null)
    {
        base.Cancel(context);
    }
    else
    {
        context.CancelChildren();
    }
}

NativeActivity 派生アクティビティでは、IsCancellationRequested プロパティを調べることによって取り消しが要求されたかどうかを確認し、MarkCanceled メソッドを呼び出して取り消し対象としてマークできます。MarkCanceled を呼び出した後、すぐにアクティビティが完了するわけではありません。ランタイムは通常どおり、未処理の処理がなくなってからアクティビティを完了します。ただし、MarkCanceled が呼び出された場合は、最終的な状態が Closed ではなく Canceled になります。

AsyncCodeActivity を使用した取り消し

AsyncCodeActivity ベースのアクティビティでも、Cancel メソッドをオーバーライドしてカスタムの取り消しロジックを指定できます。このメソッドがオーバーライドされていない場合は、アクティビティが取り消されても取り消し処理は実行されません。次の例では、AsyncCodeActivity ベースのカスタム ExecutePowerShell アクティビティの Cancel オーバーライドが定義されます。アクティビティが取り消されると、必要な取り消し動作が実行されます。この例は、「InvokePowerShell アクティビティの使用」のサンプルの一部です。

// Called by the runtime to cancel the execution of this asynchronous activity.
protected override void Cancel(AsyncCodeActivityContext context)
{
    Pipeline pipeline = context.UserState as Pipeline;
    if (pipeline != null)
    {
        pipeline.Stop();
        DisposePipeline(pipeline);
    }
    base.Cancel(context);
}

AsyncCodeActivity 派生アクティビティでは、IsCancellationRequested プロパティを調べることによって取り消しが要求されたかどうかを確認し、MarkCanceled メソッドを呼び出して取り消し対象としてマークできます。