一
受到講述最新版Workflow Foundation的<<WF本質論>>(WF3.0 3.5)這 本書的啟發,我不由自主的要為WF4.0寫點等同的文章。雖然說基本工作原理根本上相同的 ,但是編程的模型卻相差甚遠(WF3.0與WF4.0 之間)。本篇文章中,我們也將看到WF4是 如何作出了設計決策。
首先,讓我們復習一下底層的CLR技術Continuation。Continuation能讓保存恢復執行 ,因此,它需要包含可執行代碼的指針。委托用來實現這個目的。委托有一個很大的優點 ,它能夠雙向地存儲和恢復二進制文件,存儲的文件仍然可以繼續執行。下面代碼段顯示 委托的雙程運作。
Code Sample 1: SerializableDelegate
序列化委托提供了一個機制,能讓我們暫停運行托管的線程,在另一個進程中恢復( 也許在另一台計算機)。這樣做有很多好處。其中最重要的是,我們移除了'親和力'。代 碼不再堅守在原來的進程,甚至原來的機器。這樣允許我們通過簡單的添加的計算機來擴 展應用程序。此外,我們現在有一些控制。例如,我們可以刪除序列化的委托,而不是恢 復。這樣,我們是取消了執行。同樣,我們可以暫停幾天執行,而且不必擔心內存的使用 。讓我們看看這個代碼示例,看看如何序列化的委托,允許我們把一個程序分成幾個進程 。
Code Sample: Version 1
運行這個程序三次,你將會在控制台上看見“Hello world to homemade workflow foundation!” 輸出。上面的代碼已經為我們設計出第一個最原始的工作流應用程序。不 用說,這是非常的粗糙,有很多需要改進。首先,我第一步進行可擴張性地分離(事實上 ,現在我們是在業務邏輯中序列化委托的)。為了實現這個,我添加一個名字為 ReadReadWrite類。將業務邏輯移動到這個類裡面。這是我們的第二版:
Code Sample: Version 2
Version 2中,首先我在主程序裡面放置了一個while 循環這樣就不用去運行程序三次。這樣就比較方便。這是非常容易讓我們了解到將它們分 解成不同的程序,但我們沒有這樣做。重構是最簡單的,但是在這裡有一點是值得注意的 ,在ReadReadWrite中使用了 NextDelegate屬性,這是為了讓所有的委托能分享一個統一 的簽名。
現在宿主程序和業務邏輯是分開的。在這一點上,我們仍然有兩個宿主 和業務邏輯之間的耦合。宿主需要知道是從RunStep1開始的。宿主還需要知 NextDelegate是存儲延續的屬性。這些耦合使得宿主不通用。我們可以消除這些耦合通過 為ReadReadWrite定義一個基類,我們稱它為 Activity。
Code Sample: Version 3
重構很簡單。看下ReadReadWrite,現在這些代碼很 容易寫了。有一點我們都不喜歡的,Execute方法和RunStep2方法都是讀取文件的代碼, 最好是將邏輯分享到可重用組件中。為了處理這個問題,實際上我們了解到存在兩個障礙 。一個是他們是更新不同states,以及返回不同的委托。它們返回不同的委托,事實上也 是一個問題。因為這些委托是真正的控制邏輯,都是讀取文件。我們把他們放在一起,這 樣邏輯就不連貫。現在,我們通過拆分結構,進一步優化他們。
Code Sample: Version 4
寫Sequence活動是不簡單的事情,現在離理想情況還 很遠。我們將Sequence的優化推遲到下節討論。現在我想側重於移除Read1和Read2 之間 的重復。對於上面的代碼,現在Read1和Read2其實只是讀文件。最後一步,合並這兩個類 ,使它們通過名稱訪問states,而不是一個靜態字段。
Code Sample: Version 5
現在我們已經訪問這個用於編寫可重用的活動states 。這些活動不必擔心自己的序列化。如果沒有閱讀的Main的代碼,甚至不知道序列化的發 生。Read或Write,看是否像我們Activity的 API?在本系列的後面,我們將繼續這方面 的例子,說明為什麼Execute方法還有一個的參數,以及如何從數據中分離出程序。
二
繼續我們的上一篇文章,我們工作是將程序和數據分開。從一個程序員 的角度來看程序和數據是非常不同的概念。例如,您可以運行代碼,但不能運行數據。在 運行時代碼將不會被改變(除非一些情況,如hook和overlay),但數據將會一直在改變 。從處理器的角度來看,他們沒有什麼不同。他們只是一些字節。從操作系統的角度來看 ,讀取代碼事實上是優化主內存的使用。因為代碼的單個副本可以運行於多個進程。
完全相同的觀點同樣適用於工作流。我們認為,Sequence作為程序,而 sequenceCounter只是執行數據。sequenceCounter 被定義在Sequence類中也使他們密不 可分。考慮到多個工作流程會運行在相同的流程定義,sequenceCounter就會混淆,造成 一些問題。
閱讀一下Activity的代碼, States被定義在工作流中其實是個很大的問題。我們無需 將它定義成活動的保護屬性字段,而是可以從Execute方法參數中檢索。同樣,我們也不 應將NextDelegate定義在活動中。這樣會越來越復雜,因為Sequence執行需要存儲兩個委 托對象。為了解決這個問題,我們將使用Stack<Frame>持有這些對象。建議勤奮的 讀者自己試驗一下,因為我們從這個版本到下一個版本是一個很大的飛躍(而不是小小的 重構)如下所示。
Code Sample: Version 6
我試圖保持從版本5到版本6盡可能小的修改。由於我們將委托儲存從Activity移動到 states中,我們還優化Sequence的執行。現在,執行特定的活動將不會通過sequence,而 只是將sequence的延續保存在堆棧中。特別地,我們將會把更多的邏輯放在states中,使 堆棧是由states維護,而不是由Sequence和Program分配。使Frames為一個私有字段,驅 動所有一切。
版本7沒有結構性變化,只是堆棧封裝。
Code Sample: Version 7
我們已經非常接近完成了。現在對於states對象序列化,我們應該完成的分離嗎?答 案是No。這是因為states包含對委托的引用,然後委托將鏈接回該程序。為了打破這種聯 系,我們不能再序列化委托了。我們將序列化的MethodInfo和活動,以及活動的ID,活動 的ID可以通過活動樹的遍歷取得。我們將跳過這,因為這個使示例復雜了。另一個問題是 程序沒有被限定為只讀。有多種解決問題的辦法。WF3選擇使用了程序的一個副本,並始 終運行這個副本,同樣WF4選擇使用程序的一個副本和在運行時驗證主程序是不能修改的 。當程序在運行時改變,我們可以使報錯給用戶。這樣又使示例復雜了,在這裡我們將停 止這方面的討論。嘗試執行或閱讀怎麼去實現的是一個很好的學習方法。
另一個程序和數據大問題,是一行程序在一個進程中可以在執行多次。最簡單的例子 是一個循環。這些數據被存儲在frame中而不是states。事實上,所有states應被存儲在 一個frame中。它又儲存states本身,使它為全局變量。有了frame,我們可以控制的變量 范圍。這是一個相對上一個版本WF的很大的進步。
看看States實現情況。其中一個問題是ScheduleActivity只能調用一次,在接下來的 文章,我會談談提高ScheduleActivity,允許它有多個未完成的活動和工作流程,以及通 過書簽進行宿主通訊。
三
繼續我們的上一篇文章中,我們工作是允許並行和創建工作流程/宿主通信模式。這種 模式實際上是非常普遍,如多個審批人等待批准文件。上面我們看到的問題是, ScheduleActivity只能調用一次,因此,最明顯的解決辦法是允許states放置多個委托。 Frames,這是一個堆棧,我們隨時訪問堆棧的頂部。為了具有並行,我們希望有一個這樣 的數據結構的堆棧,它有多個頂部。想想看,其實這僅僅是一顆樹。在我們的數據結構中 ,我們將保存所有的葉子,以及指向父frame的指針。通過多頂部,執行指向所有states 指針的活動將是不能工作的,因為活動不知道它在的frame。我們將委托將從 Action<States>改成Action<Frame> ,其余的將會變得清楚。
Code Sample: Version 8
因為我們想保證安全,我們使用的是舊的活動,在上個示例中沒有並行的功能,現在 我們將介紹並行,Parallel是最簡單的開箱的Activity ,這裡我將使用Parallel調度兩個 Read。
Code Sample: Version 9
注意盡管使用了Parallel活動,工作還是順序執行。基本上並行活動允許有多個未完 成的工作。當這些工作項目正在等待外部信號,這些外部信號都等待著所有的都結合在一 起。我們用frame作為委托依賴,但有時委托沒有准備好執行不是因為控制依賴,而是數 據依賴。這些數據最終來自宿主。來自states,我們可以發信號給宿主等待這些數據,來 自宿主,數據已准備就緒,這些依賴可能被打破。WF3通過WorkflowQueue提供了這樣的機 制,WF4通過書簽實現這一點。程序實際上只是要求一個項目時,Queue有時顯得很過分。
Code Sample: Version 10
這將是我們的home made WF系列的結束,當然我們還有很長的路要走。但我猜直至現 在你應該可以欣賞WF帶給您什麼,以及如何最好地為您的應用程序服務。
示例代 碼:http://files.cnblogs.com/zhuqil/HomeMadeWF.zip