WorkflowInvoker と WorkflowApplication の使用

Windows Workflow Foundation (WF) には、ワークフローを ホストする方法がいくつか用意されています。 WorkflowInvoker は、メソッド呼び出しのようにワークフローを呼び出す簡単な方法を提供し、永続化を使用しないワークフローのみに使用できます。 WorkflowApplication は、ライフサイクル イベントの通知、実行制御、ブックマークの再開、および永続化を含むワークフローを実行するための豊富なモデルを提供します。 WorkflowServiceHost は、メッセージング アクティビティをサポートし、主にワーク フロー サービスと一緒に使用されます。 このトピックでは、WorkflowInvokerWorkflowApplication を使用したワークフロー ホスティングついて説明します。 WorkflowServiceHost を使用したワークフローのホストの詳細については、「ワークフロー サービス」と「ワークフロー サービスのホストの概要」を参照してください。

WorkflowInvoker の使用

WorkflowInvoker は、メソッドを呼び出すようにワークフローを実行するモデルを提供します。 WorkflowInvoker を使用してワークフローを呼び出すには、Invoke メソッドを呼び出し、呼び出すワークフローのワークフロー定義を渡します。 次の例では、WriteLine を使用して WorkflowInvoker アクティビティを呼び出します。

Activity wf = new WriteLine
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

WorkflowInvoker を使用してワークフローを呼び出すと、呼び出し元のスレッドでワークフローが実行され、ワークフローが完了するまで (アイドル時間を含む) Invoke メソッドがブロックされます。 ワークフローを完了しなければならないタイムアウト期間を構成するには、Invoke パラメーターを受け取る TimeSpan オーバーロードのいずれかを使用します。 この例では、2 つの異なるタイムアウト期間を使用してワークフローを 2 回呼び出します。 最初のワークフローは完了しますが、2 回目は完了しません。

Activity wf = new Sequence()
{
    Activities =
    {
        new WriteLine()
        {
            Text = "Before the 1 minute delay."
        },
        new Delay()
        {
            Duration = TimeSpan.FromMinutes(1)
        },
        new WriteLine()
        {
            Text = "After the 1 minute delay."
        }
    }
};

// This workflow completes successfully.
WorkflowInvoker.Invoke(wf, TimeSpan.FromMinutes(2));

// This workflow does not complete and a TimeoutException
// is thrown.
try
{
    WorkflowInvoker.Invoke(wf, TimeSpan.FromSeconds(30));
}
catch (TimeoutException ex)
{
    Console.WriteLine(ex.Message);
}

注意

TimeoutException がスローされるのは、タイムアウト期間が経過してワークフローが実行中にアイドル状態になった場合だけです。 指定されたタイムアウト時間内には完了しないワークフローが正常に完了するのは、アイドル状態にならない場合です。

WorkflowInvoker も非同期バージョンのメソッド呼び出しを提供します。 詳細については、次のトピックを参照してください。 InvokeAsync および BeginInvoke

ワークフローの入力引数の設定

ワークフローの入力引数にマップされ、引数名によってキー指定されている入力パラメーターの辞書を使用して、データをワークフローに渡すことができます。 次の例では、WriteLine が呼び出され、その Text 引数の値は入力パラメーターの辞書を使用して指定されています。

Activity wf = new WriteLine();

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World.");

WorkflowInvoker.Invoke(wf, inputs);

ワークフローの出力引数の取得

ワークフローの出力パラメーターは、Invoke の呼び出しから返される出力辞書を使用して取得できます。 次の例は、2 つの入力引数と 2 つの出力引数を持つ 1 つの Divide アクティビティで構成されるワークフローを呼び出します。 ワークフローを呼び出すと、arguments 辞書が渡されます。ここには引数名でキー指定された各入力引数の値が含まれています。 Invoke の呼び出しから制御が戻るときに、同様に引数名でキー指定された各出力引数が、outputs 辞書で返されます。

public sealed class Divide : CodeActivity
{
    [RequiredArgument]
    public InArgument<int> Dividend { get; set; }

    [RequiredArgument]
    public InArgument<int> Divisor { get; set; }

    public OutArgument<int> Remainder { get; set; }
    public OutArgument<int> Result { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        int quotient = Dividend.Get(context) / Divisor.Get(context);
        int remainder = Dividend.Get(context) % Divisor.Get(context);

        Result.Set(context, quotient);
        Remainder.Set(context, remainder);
    }
}
int dividend = 500;
int divisor = 36;

Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);

IDictionary<string, object> outputs =
    WorkflowInvoker.Invoke(new Divide(), arguments);

Console.WriteLine("{0} / {1} = {2} Remainder {3}",
    dividend, divisor, outputs["Result"], outputs["Remainder"]);

ワークフローが CodeActivity<TResult>Activity<TResult> などの ActivityWithResult から派生し、適切に定義された Result 出力引数以外にも出力引数がある場合は、Invoke の非ジェネリック オーバーロードを使用して、追加の引数を取得する必要があります。 これを行うには、Invoke に渡されるワークフロー定義は Activity 型である必要があります。 この例では、Divide アクティビティは CodeActivity<int> から派生していますが、Activity として宣言されているため、1 つの戻り値ではなく引数の辞書を返す、Invoke の非ジェネリック オーバーロードが使用されます。

public sealed class Divide : CodeActivity<int>
{
    public InArgument<int> Dividend { get; set; }
    public InArgument<int> Divisor { get; set; }
    public OutArgument<int> Remainder { get; set; }

    protected override int Execute(CodeActivityContext context)
    {
        int quotient = Dividend.Get(context) / Divisor.Get(context);
        int remainder = Dividend.Get(context) % Divisor.Get(context);

        Remainder.Set(context, remainder);

        return quotient;
    }
}
int dividend = 500;
int divisor = 36;

Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);

Activity wf = new Divide();

IDictionary<string, object> outputs =
    WorkflowInvoker.Invoke(wf, arguments);

Console.WriteLine("{0} / {1} = {2} Remainder {3}",
    dividend, divisor, outputs["Result"], outputs["Remainder"]);

WorkflowApplication の使用

WorkflowApplication は、ワークフロー インスタンス管理を実現する豊富な機能を備えています。 WorkflowApplication は実際の WorkflowInstance に対してスレッド セーフなプロキシとして動作し、ランタイムをカプセル化します。また、ワークフロー インスタンスの作成と読み込み、ライフサイクル イベントの一時停止と再開、終了、および通知を行うメソッドを提供します。 WorkflowApplication を使用してワークフローを実行するには、WorkflowApplication を作成して必要なライフサイクル イベントに定期受信し、ワークフローを開始して、それが終了するまで待機します。 この例では、WriteLine アクティビティから成るワークフロー定義が作成され、そのワークフロー定義を使用して WorkflowApplication が作成されます。 ワーク フローが完了したときにホストに通知されるように Completed が処理され、Run の呼び出しによってワーク フローが開始され、ホストはワークフローが完了するのを待ちます。 次の例に示すように、ワークフローが完了すると AutoResetEvent が設定され、ホスト アプリケーションは実行を再開できます。

AutoResetEvent syncEvent = new AutoResetEvent(false);

Activity wf = new WriteLine
{
    Text = "Hello World."
};

// Create the WorkflowApplication using the desired
// workflow definition.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Handle the desired lifecycle events.
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    syncEvent.Set();
};

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

// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();

WorkflowApplication のライフサイクル イベント

Completed の他にも、ワークフローがアンロードされたとき (Unloaded)、中止されたとき (Aborted)、アイドル状態になったとき (Idle および PersistableIdle)、または未処理の例外が発生したとき (OnUnhandledException) にホストの作成者に通知されます。 次の例に示すように、ワークフロー アプリケーションの開発者はこれらの通知を処理して適切なアクションを行うことができます。

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);

        // Outputs can be retrieved from the Outputs dictionary,
        // keyed by argument name.
        // Console.WriteLine("The winner is {0}.", e.Outputs["Winner"]);
    }
};

wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
    // Display the exception that caused the workflow
    // to abort.
    Console.WriteLine("Workflow {0} Aborted.", e.InstanceId);
    Console.WriteLine("Exception: {0}\n{1}",
        e.Reason.GetType().FullName,
        e.Reason.Message);
};

wfApp.Idle = delegate(WorkflowApplicationIdleEventArgs e)
{
    // Perform any processing that should occur
    // when a workflow goes idle. If the workflow can persist,
    // both Idle and PersistableIdle are called in that order.
    Console.WriteLine("Workflow {0} Idle.", e.InstanceId);
};

wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
{
    // Instruct the runtime to persist and unload the workflow.
    // Choices are None, Persist, and Unload.
    return PersistableIdleAction.Unload;
};

wfApp.Unloaded = delegate(WorkflowApplicationEventArgs e)
{
    Console.WriteLine("Workflow {0} Unloaded.", e.InstanceId);
};

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

    Console.WriteLine("ExceptionSource: {0} - {1}",
        e.ExceptionSource.DisplayName, e.ExceptionSourceInstanceId);

    // Instruct the runtime to terminate the workflow.
    // Other choices are Abort and Cancel. Terminate
    // is the default if no OnUnhandledException handler
    // is present.
    return UnhandledExceptionAction.Terminate;
};

ワークフローの入力引数の設定

WorkflowInvoker を使用しているときにデータを渡す方法と同様に、起動時にパラメーターの辞書を使用してデータをワークフローに渡すことができます。 辞書の各項目は、指定されたワークフローの入力引数にマップされます。 次の例では、WriteLine アクティビティから成るワークフローが呼び出され、その Text 引数が入力パラメーターの辞書を使用して指定されます。

AutoResetEvent syncEvent = new AutoResetEvent(false);

Activity wf = new WriteLine();

// Create the dictionary of input parameters.
Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World!");

// Create the WorkflowApplication using the desired
// workflow definition and dictionary of input parameters.
WorkflowApplication wfApp = new WorkflowApplication(wf, inputs);

// Handle the desired lifecycle events.
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
    syncEvent.Set();
};

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

// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();

ワークフローの出力引数の取得

ワークフローが完了したら、WorkflowApplicationCompletedEventArgs.Outputs 辞書にアクセスして、Completed ハンドラーの出力引数を取得できます。 次の例では、WorkflowApplication を使用してワークフローをホストしています。 1 つの DiceRoll アクティビティで構成されるワークフロー定義を使用して WorkflowApplication インスタンスが構築されます。 DiceRoll アクティビティには、サイコロ振り操作の結果を表す 2 つの出力引数があります。 ワークフローが完了すると、この出力が Completed ハンドラーで取得されます。

public sealed class DiceRoll : CodeActivity
{
    public OutArgument<int> D1 { get; set; }
    public OutArgument<int> D2 { get; set; }

    static Random r = new Random();

    protected override void Execute(CodeActivityContext context)
    {
        D1.Set(context, r.Next(1, 7));
        D2.Set(context, r.Next(1, 7));
    }
}
 // Create a WorkflowApplication instance.
 WorkflowApplication wfApp = new WorkflowApplication(new DiceRoll());

 // 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);

         // Outputs can be retrieved from the Outputs dictionary,
         // keyed by argument name.
         Console.WriteLine("The two dice are {0} and {1}.",
             e.Outputs["D1"], e.Outputs["D2"]);
     }
 };

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

注意

WorkflowApplicationWorkflowInvoker は入力引数の辞書を取得し、out 引数の辞書を返します。 これらの辞書のパラメーター、プロパティ、および戻り値は IDictionary<string, object> 型です。 渡される辞書クラスの実際のインスタンスには、IDictionary<string, object> を実装した任意のクラスを使用できます。 これらの例では、Dictionary<string, object> が使用されています。 ディクショナリの詳細については、IDictionary<TKey,TValue>Dictionary<TKey,TValue> に関するそれぞれのページを参照してください。

ブックマークを使用した実行中のワークフローへのデータの受け渡し

ブックマークは、アクティビティが再開されるのを受動的に待機するメカニズムです。また、実行中のワークフロー インスタンスにデータを渡すメカニズムでもあります。 次の例に示すように、アクティビティがデータを待機している場合、Bookmark を作成し、Bookmark が再開されたときに呼び出されるコールバック メソッドを登録します。

public sealed class ReadLine : NativeActivity<string>
{
    [RequiredArgument]
    public InArgument<string> BookmarkName { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        // Create a Bookmark and wait for it to be resumed.
        context.CreateBookmark(BookmarkName.Get(context),
            new BookmarkCallback(OnResumeBookmark));
    }

    // NativeActivity derived activities that do asynchronous operations by calling
    // one of the CreateBookmark overloads defined on System.Activities.NativeActivityContext
    // must override the CanInduceIdle property and return true.
    protected override bool CanInduceIdle
    {
        get { return true; }
    }

    public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
    {
        // When the Bookmark is resumed, assign its value to
        // the Result argument.
        Result.Set(context, (string)obj);
    }

実行されると、ReadLine アクティビティは Bookmark を作成し、コールバックを登録してから、Bookmark が再開されるのを待機します。 再開されると、ReadLine アクティビティは Bookmark と一緒に渡されたデータを Result 引数に代入します。 次の例では、ReadLine アクティビティを使用してユーザー名を収集し、それをコンソール ウィンドウに表示するワークフローを作成します。

Variable<string> name = new Variable<string>();

Activity wf = new Sequence
{
    Variables = { name },
    Activities =
     {
         new WriteLine
         {
             Text = "What is your name?"
         },
         new ReadLine
         {
             BookmarkName = "UserName",
             Result = new OutArgument<string>(name)
         },
         new WriteLine
         {
             Text = new InArgument<string>((env) =>
                 ("Hello, " + name.Get(env)))
         }
     }
};

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

// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);

wfApp.Idle = delegate(WorkflowApplicationIdleEventArgs e)
{
    idleEvent.Set();
};

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

// Wait for the workflow to go idle before gathering
// the user's input.
idleEvent.WaitOne();

// Gather the user's input and resume the bookmark.
// Bookmark resumption only occurs when the workflow
// is idle. If a call to ResumeBookmark is made and the workflow
// is not idle, ResumeBookmark blocks until the workflow becomes
// idle before resuming the bookmark.
BookmarkResumptionResult result = wfApp.ResumeBookmark("UserName",
    Console.ReadLine());

// Possible BookmarkResumptionResult values:
// Success, NotFound, or NotReady
Console.WriteLine("BookmarkResumptionResult: {0}", result);

ReadLine アクティビティが実行されると、Bookmark という名前の UserName が作成され、ブックマークが再開されるのを待機します。 ホストは必要なデータを収集し、Bookmark を再開します。 ワークフローが再開されると名前が表示されて、完了します。

ホスト アプリケーションでは、ワークフローを調べて、アクティブなブックマークがあるかどうかを確認できます。 この操作を実行するには、GetBookmarks インスタンスの WorkflowApplication メソッドを呼び出すか、WorkflowApplicationIdleEventArgs ハンドラーの Idle を調べます。

次のコード例は前の例と似ていますが、ブックマークを再開する前にアクティブなブックマークを列挙する点が異なります。 ワークフローが開始され、Bookmark が作成されてワークフローがアイドル状態になると、GetBookmarks が呼び出されます。 このワークフローが完了すると、次の出力がコンソールに表示されます。

お名前は何ですか。
BookmarkName: UserName - OwnerDisplayName: ReadLineSteveHello, Steve

Variable<string> name = new Variable<string>();

Activity wf = new Sequence
{
    Variables = { name },
    Activities =
     {
         new WriteLine
         {
             Text = "What is your name?"
         },
         new ReadLine
         {
             BookmarkName = "UserName",
             Result = new OutArgument<string>(name)
         },
         new WriteLine
         {
             Text = new InArgument<string>((env) =>
                 ("Hello, " + name.Get(env)))
         }
     }
};

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

// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);

wfApp.Idle = delegate(WorkflowApplicationIdleEventArgs e)
{
    // You can also inspect the bookmarks from the Idle handler
    // using e.Bookmarks

    idleEvent.Set();
};

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

// Wait for the workflow to go idle and give it a chance
// to create the Bookmark.
idleEvent.WaitOne();

// Inspect the bookmarks
foreach (BookmarkInfo info in wfApp.GetBookmarks())
{
    Console.WriteLine("BookmarkName: {0} - OwnerDisplayName: {1}",
        info.BookmarkName, info.OwnerDisplayName);
}

// Gather the user's input and resume the bookmark.
wfApp.ResumeBookmark("UserName", Console.ReadLine());

WorkflowApplicationIdleEventArgs インスタンスの Idle ハンドラーに渡される WorkflowApplication を調べるコード サンプルを次に示します。 この例では、アイドル状態になるワークフローに、Bookmark という名前で EnterGuess というアクティビティによって所有されている 1 つの ReadInt があります。 このコード例は、「チュートリアル入門」の一部である「方法: ワークフローを実行する」に基づいています。 この例のコードを含めるようにこの手順の Idle ハンドラーを変更すると、次の出力が表示されます。

BookmarkName: EnterGuess - OwnerDisplayName: ReadInt

wfApp.Idle = delegate(WorkflowApplicationIdleEventArgs e)
{
    foreach (BookmarkInfo info in e.Bookmarks)
    {
        Console.WriteLine("BookmarkName: {0} - OwnerDisplayName: {1}",
            info.BookmarkName, info.OwnerDisplayName);
    }

    idleEvent.Set();
};

まとめ

WorkflowInvoker はワークフローを呼び出す簡単な方法を提供します。また、ワークフローの開始時にデータを渡し、完了したワークフローからデータを抽出する方法を提供しますが、WorkflowApplication を使用できるような複雑なシナリオには使用できません。