使用 WorkflowInvoker 與 WorkflowApplication
Windows Workflow Foundation (WF) 提供數種裝載工作流程的方法。 WorkflowInvoker 提供一種簡單方法來叫用工作流程,如同方法呼叫一般,但只能用於不使用持續性的工作流程。 WorkflowApplication 提供更豐富的模型,可執行包含生命週期事件通知、執行控制、書籤繼續以及持續性的工作流程。 WorkflowServiceHost 支援傳訊活動,主要搭配工作流程服務使用。 本主題會向您介紹如何使用 WorkflowInvoker 和 WorkflowApplication 進行工作流程裝載。 如需使用 WorkflowServiceHost 的裝載工作流程詳細資訊,請參閱工作流程服務和裝載工作流程服務概觀。
使用 WorkflowInvoker
WorkflowInvoker如同方法叫用般,提供執行工作流程的模型。 若要使用 WorkflowInvoker 叫用工作流程,請呼叫 Invoke 方法,並傳入要叫用之工作流程的工作流程定義。 在此範例中,會使用 WriteLine 叫用 WorkflowInvoker 活動。
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
使用 WorkflowInvoker 叫用工作流程時,工作流程會在呼叫執行緒上執行,且 Invoke 方法會封鎖至工作流程完成為止,並包含任何閒置時間。 若要設定工作流程必須完成的逾時間隔,請使用接受 Invoke 的其中一個 TimeSpan 多載。 在此範例中,會以兩個不同的逾時間隔叫用工作流程兩次。 第一個工作流程會完成,但是第二個則不會。
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 呼叫傳回的輸出字典來取得。 下列範例會叫用由具有兩個輸入引數和兩個輸出引數之單一 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"]);
如果此工作流程衍生自 ActivityWithResult (例如 CodeActivity<TResult>
或 Activity<TResult>
),而且除了定義完善的 Result 輸出引數以外,還有其他輸出引數,您就必須使用 Invoke
的非泛型多載,才能擷取其他引數。 若要這樣做,傳遞給 Invoke
的工作流程定義必須屬於 Activity 型別。 在此範例中,Divide
活動會衍生自 CodeActivity<int>
但宣告為 Activity,以便使用 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 的執行緒安全 Proxy (可封裝執行階段),並提供建立及載入工作流程執行個體、暫停與繼續、終止,以及生命週期事件通知的方法。 若要使用 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();
工作流程應用程式開發週期事件
除了 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();
擷取工作流程的輸出引數
當工作流程完成時,可存取 Completed 字典來擷取 WorkflowApplicationCompletedEventArgs.Outputs 處理常式中的任何輸出引數。 下列範例會使用 WorkflowApplication 來裝載工作流程。 系統會使用由單一 DiceRoll
活動組成的工作流程定義來建構 WorkflowApplication 執行個體。 DiceRoll
活動具有兩個輸出引數,這些引數代表擲骰作業的結果。 工作流程完成時,便會在 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();
注意
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: {0}", result);
當執行 ReadLine
活動時,它會建立稱為 Bookmark 的 UserName
,然後等候書籤繼續。 主機會收集想要的資料,然後繼續 Bookmark。 工作流程會繼續、顯示名稱,然後完成。
主應用程式可以檢查工作流程,以判斷是否有任何作用中的書籤。 若要進行這項處理,它可以呼叫 GetBookmarks 執行個體的 WorkflowApplication 方法,或是在 WorkflowApplicationIdleEventArgs 處理常式中檢查 Idle。
下列程式碼範例與上一個範例很相似,例外之處是先列舉作用中的書籤,然後再繼續書籤。 系統會啟動工作流程,而且一旦建立 Bookmark 並且工作流程處於閒置狀態之後,就會呼叫 GetBookmarks。 當工作流程完成時,主控台就會顯示下列輸出。
What is your name?
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
之活動所擁有的 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 的更複雜案例。