Windows Workflow Foundation (WF) には、ワークフローをホストするいくつかの方法が用意されています。 WorkflowInvoker は、メソッド呼び出しであるかのようにワークフローを呼び出すための簡単な方法を提供し、永続化を使用しないワークフローにのみ使用できます。 WorkflowApplication には、ライフサイクル イベントの通知、実行制御、ブックマークの再開、永続化を含むワークフローを実行するための豊富なモデルが用意されています。 WorkflowServiceHost はメッセージング アクティビティのサポートを提供し、主にワークフロー サービスで使用されます。 このトピックでは、 WorkflowInvoker と WorkflowApplicationを使用したワークフロー ホスティングについて説明します。 WorkflowServiceHostを使用してワークフローをホストする方法の詳細については、「ワークフロー サービスとホスティング ワークフロー サービスの概要」を参照してください。
WorkflowInvoker の使用
WorkflowInvoker は、メソッド呼び出しであるかのようにワークフローを実行するためのモデルを提供します。 WorkflowInvokerを使用してワークフローを呼び出すには、Invoke メソッドを呼び出し、呼び出すワークフローのワークフロー定義を渡します。 この例では、WorkflowInvokerを使用してWriteLine アクティビティが呼び出されます。
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
WorkflowInvokerを使用してワークフローが呼び出されると、ワークフローは呼び出し元のスレッドで実行され、Invoke メソッドは、アイドル時間を含め、ワークフローが完了するまでブロックします。 ワークフローを完了する必要があるタイムアウト間隔を構成するには、TimeSpan パラメーターを受け取るInvokeオーバーロードのいずれかを使用します。 この例では、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($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");
ワークフローが ActivityWithResult ( CodeActivity<TResult>
や Activity<TResult>
など) から派生し、明確に定義された 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($"{dividend} / {divisor} = {outputs["Result"]} Remainder {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 {e.InstanceId} Terminated.");
Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
}
else if (e.CompletionState == ActivityInstanceState.Canceled)
{
Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
}
else
{
Console.WriteLine($"Workflow {e.InstanceId} Completed.");
// Outputs can be retrieved from the Outputs dictionary,
// keyed by argument name.
// Console.WriteLine($"The winner is {e.Outputs["Winner"]}.");
}
};
wfApp.Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
{
// Display the exception that caused the workflow
// to abort.
Console.WriteLine($"Workflow {e.InstanceId} Aborted.");
Console.WriteLine($"Exception: {e.Reason.GetType().FullName}\n{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 {e.InstanceId} Idle.");
};
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 {e.InstanceId} Unloaded.");
};
wfApp.OnUnhandledException = delegate (WorkflowApplicationUnhandledExceptionEventArgs e)
{
// Display the unhandled exception.
Console.WriteLine($"OnUnhandledException in Workflow {e.InstanceId}\n{e.UnhandledException.Message}");
Console.WriteLine($"ExceptionSource: {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を使用してワークフローをホストします。
WorkflowApplication インスタンスは、1 つのDiceRoll
アクティビティで構成されるワークフロー定義を使用して構築されます。
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 {e.InstanceId} Terminated.");
Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
}
else if (e.CompletionState == ActivityInstanceState.Canceled)
{
Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
}
else
{
Console.WriteLine($"Workflow {e.InstanceId} Completed.");
// Outputs can be retrieved from the Outputs dictionary,
// keyed by argument name.
Console.WriteLine($"The two dice are {e.Outputs["D1"]} and {e.Outputs["D2"]}.");
}
};
// Run the workflow.
wfApp.Run();
注
WorkflowApplication を WorkflowInvoker 入力引数のディクショナリを受け取り、 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: {result}");
ReadLine
アクティビティが実行されると、Bookmarkという名前のUserName
が作成され、ブックマークが再開されるまで待機します。 ホストは目的のデータを収集し、 Bookmarkを再開します。 ワークフローが再開され、名前が表示され、完了します。
ホスト アプリケーションは、ワークフローを調べて、アクティブなブックマークがあるかどうかを判断できます。 これを行うには、WorkflowApplication インスタンスのGetBookmarks メソッドを呼び出すか、Idle ハンドラーでWorkflowApplicationIdleEventArgsを調べます。
次のコード例は前の例に似ていますが、ブックマークが再開される前にアクティブなブックマークが列挙される点が異なります。 ワークフローが開始され、 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: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
}
// Gather the user's input and resume the bookmark.
wfApp.ResumeBookmark("UserName", Console.ReadLine());
次のコード例では、WorkflowApplication インスタンスのIdle ハンドラーに渡されるWorkflowApplicationIdleEventArgsを調べます。 この例では、アイドル状態になるワークフローには、ReadInt
という名前のアクティビティによって所有される、EnterGuess
の名前を持つ 1 つのBookmarkがあります。 このコード例は、作業の開始チュートリアルの一部であるワークフローを実行する方法に基づいています。 その手順の Idle ハンドラーがこの例のコードを含むよう変更されると、次の出力が表示されます。
BookmarkName: EnterGuess - OwnerDisplayName: ReadInt
wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
foreach (BookmarkInfo info in e.Bookmarks)
{
Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
}
idleEvent.Set();
};
概要
WorkflowInvoker は、ワークフローを呼び出すための軽量な方法を提供します。また、ワークフローの開始時にデータを渡し、完成したワークフローからデータを抽出するためのメソッドを提供しますが、 WorkflowApplication を使用できる複雑なシナリオは提供されません。
.NET