探秘WF4 Beta2中工作流對(duì)象模型
本文將討論WF4 Beta2中的工作流對(duì)象模型,在.NET 4.0中工作流是變化比較大的一部分,希望那個(gè)通過本文能讓大家對(duì)WF4 Beta2有一個(gè)全新的認(rèn)識(shí)。
隨著Visual Studio2010 BETA2的發(fā)布,大家對(duì).NET 4.0技術(shù)的研究熱情隨之高漲。在整個(gè).NET 4.0所引入的新技術(shù)中,工作流可謂是變化最大的部分。WF4與WF3幾乎可以看成是兩個(gè)完全不同的產(chǎn)品。
對(duì)于WF3的編程模型,已有相關(guān)的技術(shù)書籍介紹了,在網(wǎng)上也可以搜到有關(guān)的資源。但對(duì)于WF4,卻幾乎找不到任何深入介紹其對(duì)象模型的文章。
我以Reflector作為工具,反匯編了WF4的源代碼,通過仔細(xì)閱讀,粗步理出了一個(gè)頭緒,在本文中進(jìn)行介紹,期望能起到一個(gè)拋磚引玉的作用,幫助大家深入地把握WF4的技術(shù)內(nèi)幕。
呵呵,第一次在博客園發(fā)文,希望大家多多鼓勵(lì)。
1 Acitvity的繼承樹
在WF4中,Activity類是最頂層的基類。任何一個(gè)工作流都由至少一個(gè)Activtiy構(gòu)成。以下是WF4中Activity的繼承樹:
在真實(shí)的工作流中,各個(gè)Activity可以相互嵌套,形成一個(gè)樹型結(jié)構(gòu),最底層的葉子通常就是上圖中最底層類(如CodeActivity)的實(shí)例。
最頂層的Activity類提供了一個(gè)可以供子類重寫的InternalExecute()方法:
- internal virtual void InternalExecute(ActivityInstance instance,
- ActivityExecutor executor, BookmarkManager bookmarkManager);
子類可以重寫此方法,在此方法中實(shí)現(xiàn)各種功能,這個(gè)方法在WF4內(nèi)部非常重要,許多東西都與它相關(guān)。
為了方便地供開發(fā)者自定義業(yè)務(wù)處理邏輯,諸如CodeActivity之類最底層的類,另定義了一個(gè)抽象的Execute()方法:
- protected abstract void Execute(CodeActivityContext context);
當(dāng)開發(fā)者自定義Activity時(shí),就可以直接地重寫此方法。
簡(jiǎn)言之,工作流的運(yùn)行就體現(xiàn)為Activity對(duì)象樹中葉子節(jié)點(diǎn)Execute方法(或類似的方法,比如DynamicActivity是InternalExecute方法,AsyncCodeActivity是BeginExecute和EndExecute方法)的執(zhí)行。
2.WF4中工作流的執(zhí)行原理
首先要明確,在WF4中,如果使用WorkflowInvoker類來啟動(dòng)工作流時(shí):
- WorkflowInvoker.Invoke(new Workflow1());
工作流Workflow1將在調(diào)用者的線程中執(zhí)行。這種情況下,工作流的執(zhí)行類似于方法調(diào)用,是最簡(jiǎn)單的執(zhí)行模式。
然而,如果使用WorkflowApplication啟動(dòng)工作流,工作流實(shí)例將在調(diào)用者線程之外的另一個(gè)線程中運(yùn)行:
- WorkflowApplication wpp = new WorkflowApplication(new Workflow1());
- wpp.Run();
而且,這個(gè)“另外的工作線程”是線程池中的線程。
不管是由哪個(gè)線程負(fù)責(zé)執(zhí)行工作流,有一個(gè)原則是很重要的:
單個(gè)工作流實(shí)例是單線程執(zhí)行的,哪怕諸如Parallel Activity給你一個(gè)多分支“并行”運(yùn)行的假象。
事實(shí)上,Parallel Activity采用在單線程中“輪換執(zhí)行”各分支。當(dāng)一個(gè)分支進(jìn)入空閑“Idle”時(shí),工作流調(diào)度器調(diào)度下一分支投入運(yùn)行。所果所有分支都不包括使本分支進(jìn)入Idle狀態(tài)的Activtity(比如有一個(gè)Delay Activity或創(chuàng)建了書簽),則Parallel Activity按從左到右的順序執(zhí)行各分支。
那么,構(gòu)成工作流的各個(gè)Activity實(shí)例是如何執(zhí)行的?
WF運(yùn)行時(shí)在內(nèi)部為每個(gè)工作流維護(hù)了一個(gè)工作項(xiàng)隊(duì)列。然后,創(chuàng)建一個(gè)Scheduler類的實(shí)例來負(fù)責(zé)從此工作項(xiàng)隊(duì)列中取出和追加工作項(xiàng),并執(zhí)行之。
這里要說說這個(gè)工作項(xiàng)隊(duì)列,在Scheduler類的代碼中可以找到它的聲明:
- private Quack<WorkItem> workItemQueue;
這里有一個(gè)奇怪的Quack<T>泛型類,我仔細(xì)看了一下,其實(shí)它就是一個(gè)泛型隊(duì)列,但它有一點(diǎn)特殊之處:
Quack<T>泛型類在內(nèi)部使用一個(gè)數(shù)組來保存數(shù)據(jù):
- private T[] items;
初始時(shí),為隊(duì)列分配可容納4個(gè)T類型對(duì)象的內(nèi)存空間,當(dāng)不斷增加對(duì)象而需要擴(kuò)充空間時(shí),就分配一個(gè)“當(dāng)前所占內(nèi)存空間*2”的新數(shù)組,再將老數(shù)組中的內(nèi)容復(fù)制到新數(shù)組中。
很明顯,在兩個(gè)數(shù)組中復(fù)制元素會(huì)花費(fèi)系統(tǒng)資源,我不知道為何WF4的設(shè)計(jì)者這樣設(shè)計(jì),估計(jì)是他們有其他的考慮。
隊(duì)列中的WorkItem對(duì)象很有趣,它代表一個(gè)將被執(zhí)行的Activity實(shí)例,這里暫時(shí)放下,一會(huì)兒還會(huì)介紹它。
Scheduler對(duì)象的工作可以簡(jiǎn)述如下:
它從隊(duì)列中取出一個(gè)WorkItem對(duì)象,然后將其委托給線程池中的線程(如果工作流由WorkApplication以異步方式啟動(dòng)執(zhí)行)或調(diào)用者線程(如果工作流由WorkflowInvoker以同步方式啟動(dòng)執(zhí)行)執(zhí)行。這些線程將負(fù)責(zé)調(diào)用WorkItem所封裝的Activity實(shí)例的Execute()方法(或類似的方法,如前所述)。
3 深入分析Activity執(zhí)行的流程
一個(gè)Activity實(shí)例到底是如何執(zhí)行的?一切得從WorkItem類開始。
WorkItem是一個(gè)抽象基類,提供了幾個(gè)抽象方法,其中最重要的就是Execute()方法:
- internal abstract class WorkItem
- {
- //……
- private ActivityInstance activityInstance;
- public abstract bool Execute(ActivityExecutor executor,
- BookmarkManager bookmarkManager);
- }
上述聲明中還有兩個(gè)很重要的類ActivityInstance和ActivityExecutor。
ActivityInstance代表著正在運(yùn)行的一個(gè)Activity實(shí)例,它包容一堆的internal方法可以完成Activity的執(zhí)行(Execute)取消(Cancel)和放棄(Abort)的功能。 ActivityExecutor則負(fù)責(zé)調(diào)用ActivityInstance中的這些方法。
WorkItem有一堆的子類,這些子類又派生出“孫”類。比如,其中的一個(gè)分支如下:
不管有幾個(gè)子孫,后代一般都重寫了WorkItem所定義的Execute()抽象方法。
我們以ExecuteRootWorkItem類為例,顧名思義,這應(yīng)該是與工作流中最頂層的Activity相對(duì)應(yīng)的WorkItem。它的Execute()方法如下所示:
- public override bool Execute(ActivityExecutor executor, BookmarkManager bookmarkManager)
- {
- return base.ExecuteBody(executor, bookmarkManager, this.resultLocation);
- }
它將調(diào)用基類ExecuteActivityWorkItem的ExecuteBody()方法,此方法的關(guān)鍵代碼如下:
- protected bool ExecuteBody(ActivityExecutor executor, BookmarkManager bookmarkManager, Location resultLocation)
- {
- //……
- base.ActivityInstance.Execute(executor, bookmarkManager);
- //……
- }
可以看到,它直接跳去執(zhí)行最頂層基類WorkItem所定義的ActivityInstance對(duì)象的Execute()方法。此方法的代碼如下:
- internal void Execute(ActivityExecutor executor, BookmarkManager bookmarkManager)
- {
- //……
- this.Activity.InternalExecute(this, executor, bookmarkManager);
- }
注意ActivityInstance實(shí)際上封裝了一個(gè)Activity對(duì)象:
- public sealed class ActivityInstance : ActivityInstanceMap.IActivityReference
- {
- public Activity Activity { get; internal set; }
- //……
- }
所以,ActivityInstance對(duì)象的Execute()方法實(shí)際上執(zhí)行的是Activity對(duì)象的InternalExecute()方法。再追蹤下去:
- internal virtual void InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager)
- {
- //……
- executor.ScheduleActivity(this.runtimeImplementation, instance, null, null, null);
- }
注意:上述代碼是Acitivity對(duì)InternalExecute()默認(rèn)的實(shí)現(xiàn)方式,它的子類(比如CodeActivity)通常會(huì)重寫它。
可以看到,在ActivityInstance對(duì)象的Execute()方法中,執(zhí)行流程轉(zhuǎn)給了從前面一路傳送過來的ActivityExecutor對(duì)象,由此對(duì)象的ScheduleActivity方法負(fù)責(zé)將Activity插入到工作項(xiàng)隊(duì)列中。
ActivityExecutor.ScheduleActivity方法又進(jìn)行了一個(gè)“倒手”,調(diào)用自己的ScheduleBody()方法:
- private ActivityInstance ScheduleActivity(……)
- {
- //……
- this.ScheduleBody(scheduledInstance, requiresSymbolResolution, argumentValueOverrides, resultLocation);
- }
在ScheduleBody()方法中,“佛祖”終于現(xiàn)出真身,我們看到了Scheduler的身影:
- internal void ScheduleBody(ActivityInstance activityInstance, bool requiresSymbolResolution, IDictionary<string, object> argumentValueOverrides, Location resultLocation)
- {
- if (resultLocation == null)
- {
- //……
- this.scheduler.PushWork(new ExecuteExpressionWorkItem(activityInstance, requiresSymbolResolution, argumentValueOverrides, resultLocation));
- //……
- }
在上述代碼中,Scheduler對(duì)象將activityInstance轉(zhuǎn)換為了一個(gè)ExecuteExpressionWorkItem,然后將其插入到工作項(xiàng)隊(duì)列中等待執(zhí)行。
現(xiàn)在我們看到,默認(rèn)情況下,對(duì)ExecuteRootWorkItem的執(zhí)行將導(dǎo)致一個(gè)新的ExecuteExpressionWorkItem工作項(xiàng)被插入到工作項(xiàng)隊(duì)列中。
4.工作項(xiàng)隊(duì)列中的工作項(xiàng)是如何調(diào)度執(zhí)行的?
Scheduler類負(fù)責(zé)工作項(xiàng)的調(diào)度執(zhí)行。
在Scheduler類的構(gòu)造函數(shù)中,掛接了一個(gè)回調(diào)函數(shù)OnScheduledWork:
- static Scheduler()
- {
- //……
- onScheduledWorkCallback = Fx.ThunkCallback(new SendOrPostCallback(Scheduler.OnScheduledWork));
- }
在OnScheduledWork()函數(shù)中,揭露出了任務(wù)項(xiàng)調(diào)度是如何進(jìn)行的秘密:
- private static void OnScheduledWork(object state)
- {
- //取出隊(duì)列中的第一個(gè)工作項(xiàng)
- WorkItem firstWorkItem = scheduler.firstWorkItem;
- if ((scheduler.workItemQueue != null) && (scheduler.workItemQueue.Count > 0))
- {
- scheduler.firstWorkItem = scheduler.workItemQueue.Dequeue();
- }
- else
- {
- scheduler.firstWorkItem = null;
- }
- //執(zhí)行這一工作項(xiàng)
- continueAction = scheduler.callbacks.ExecuteWorkItem(firstWorkItem);
- //……
- }
下面是ExecuteWorkItem()方法的代碼,可以看到,最后調(diào)度器還是委托activityExecutor來執(zhí)行Activity的:
- public Scheduler.RequestedAction ExecuteWorkItem(WorkItem workItem)
- {
- Scheduler.RequestedAction objA = this.activityExecutor.OnExecuteWorkItem(workItem);
- //……
- }
- ActivityExecutor的OnExecuteWorkItem()方法有很多代碼,其中關(guān)鍵的就是以下這幾句:
- internal Scheduler.RequestedAction OnExecuteWorkItem(WorkItem workItem)
- {
- //……
- propertyManagerOwner.PropertyManager.SetupWorkflowThread();
- if ((abortException == null) && !workItem.Execute(this, this.bookmarkManager))
- {
- return Scheduler.YieldSilently;
- }
- }
- //……
- }
我們終于發(fā)現(xiàn)了調(diào)用工作項(xiàng)的Execute()方法的語句。
有的朋友可能會(huì)疑惑,我們的探索之旅從WorkItem.Execute()方法開始,轉(zhuǎn)了一圈怎么又回到了WorkItem.Execute()方法?這樣一來,調(diào)用工作項(xiàng)的WorkItem.Execute()方法將導(dǎo)致一個(gè)工作項(xiàng)被加入到隊(duì)列中,然后當(dāng)此工作項(xiàng)被執(zhí)行時(shí),它又將一個(gè)工作項(xiàng)加入到隊(duì)列中,這會(huì)不會(huì)引發(fā)無限遞歸?
事實(shí)上這正是我們想要的效果。因?yàn)橐粋€(gè)工作流實(shí)例實(shí)際上就是一個(gè)層層嵌套的遞歸的結(jié)構(gòu),這種設(shè)計(jì)使得執(zhí)行其頂層Activity對(duì)象的Execute()方法時(shí),會(huì)將其子Activity所對(duì)應(yīng)的WorkItem加入到隊(duì)列中加以遞歸執(zhí)行。
很明顯,對(duì)于那些不包容子Activity的Activity,我們應(yīng)該“打斷”這種遞歸執(zhí)行的過程。WF4是怎么做到的?
以一個(gè)實(shí)例來說明更好。 請(qǐng)看以下自定義的Activity:
- public sealed class Prompt : CodeActivity
- {
- public InArgument<string> Text { get; set; }
- protected override void Execute(CodeActivityContext context)
- {
- Console.Write(Text.Get(context));
- }
- }
注意上述Activity重寫了基類CodeActivity的Execute()方法,此方法一執(zhí)行完畢就會(huì)返回。
前面說過,對(duì)工作項(xiàng)隊(duì)列中WorkItem.Execute()方法的調(diào)用,最終將轉(zhuǎn)換為對(duì)ActivityInstance對(duì)象的Execute()方法的調(diào)用。而ActivityInstance又包容了最終的Activity對(duì)象實(shí)例,并將調(diào)用轉(zhuǎn)給這一最終對(duì)象的InternalExecute()方法,為方便起見,重貼此方法代碼如下:
- internal void Execute(ActivityExecutor executor, BookmarkManager bookmarkManager)
- {
- //……
- this.Activity.InternalExecute(this, executor, bookmarkManager);
- }
在我們的自定義Activity中,沒有重寫CodeActivity的InternalExecute()方法(事實(shí)上也不可能,因?yàn)榇朔椒ㄊ荢ealed的),所以,被調(diào)用的實(shí)際上是基類CodeActivity的InternalExecute()方法。以下是CodeActivity的InternalExecute()方法代碼:
- internal sealed override void InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager)
- {
- //……
- this.Execute(context);
- //……
- }
非常清楚,它應(yīng)用了多態(tài)特性,調(diào)用子類重寫的Execute()方法,注意,它并沒有調(diào)用最頂層Activity類所提供的InternalExecute()方法。
所以,問題的關(guān)鍵在于最頂層基類Activity的InternalExecute()方法,默認(rèn)情況下,此方法將會(huì)通過 ActivityExecutor.ScheduleActivity()方法的調(diào)用將一個(gè)工作項(xiàng)加入到隊(duì)列中,但CodeActivity沒調(diào)用Activity基類的InternalExecute()方法而是重寫了此方法,所以就打斷了“遞歸”調(diào)用鏈。
5.小結(jié)
不知道朋友們是不是有點(diǎn)昏了,沒辦法,WF4內(nèi)部就是有這么多的彎彎繞。
簡(jiǎn)單地說:工作流執(zhí)行時(shí),所有需要被執(zhí)行的Activity會(huì)被封裝為一個(gè)WorkItem,被放到一個(gè)工作項(xiàng)隊(duì)列中,然后由WF4調(diào)度器(其實(shí)就是Scheduler類的實(shí)例)負(fù)責(zé)從此隊(duì)列中取出工作項(xiàng)執(zhí)行。
工作項(xiàng)的執(zhí)行可以由線程池中的線程承擔(dān)。,也可以由調(diào)用者線程來承擔(dān)。
WF4內(nèi)部的工作原理非常復(fù)雜,事實(shí)上我們也沒有必要了解其每個(gè)技術(shù)細(xì)節(jié)。但如果能了解其大致的內(nèi)部原理還是非常有用的,它能幫助我們避開陷阱,真正地把技術(shù)用好。
對(duì)于技術(shù),不僅要知其然,還要知其所以然。才能真正擁有了自由。
原文標(biāo)題:探索WF4 Beta2的工作流對(duì)象模型
鏈接:http://www.cnblogs.com/bitfan/archive/2009/10/29/1592591.html
【編輯推薦】