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