學習完本章,你將掌握:
1.知道怎樣使用Sequence活動
2.知道怎樣使用Code活動
3.知道在工作流中怎樣拋出異常並對其進行處理
4.知道如何在代碼中暫停和終止你的工作流實例
在本章,我們將正式引入前面已經看到過的一組活動:Sequence活動和Code活動。但我相信,適當的錯誤處理對於精心設計和運行良好的軟件是至關重要的,所以我們將會研究如何使用工作流中的活動拋出異常、捕獲異常、甚至暫停和終止你的工作流。我們就從Sequence活動開始吧。
使用順序活動對象
實際上,說我們已見過Sequence活動並不完全正確。我們創建工作流應用程序時實際上使用的是SequentialWorkflow活動,但大體的意思是一樣的:這個活動包含其它依次要執行的活動。這一點可和使用parallel活動的並行執行相對比,在第11章(“Parallel活動”)中我們將看到parallel活動。
當你以特定的順序執行任務時,你必須依次完成這些任務,這點通常是必須的。
Sequence活動是一個組合活動,我們在第四章(“活動和工作流類型介紹”)中已經簡要討論過。它包含其它活動,這些活動一定要按次序執行。你可在父Sequence活動內放入包含parallel活動在內的其它組合活動。但子活動要依次地,一個接一個地執行,即使這些子活動本身包含的並行執行流也如此。
我們就來使用Sequence活動創建一個簡單的工作流。我們將再次使用Code活動,關於它的更詳細的細節將在下一節“使用Code活動”進行討論。為對特定的工作流活動的行為進行了解,我們將回到基於控制台的應用程序中。對於基於控制台的應用程序,通常你需要書寫的代碼更少,因為你不用對用戶界面進行處理。(但隨著本書的進展,我們也會創建其它的圖形化的測試案例。)
創建一個使用了Sequence活動的工作流
1.下載本章的源代碼,本例的最終版本在“Sequencer Completed”目錄下,可使用Visual Studio 2008打開並直接查看它的運行結果。“Sequencer”目錄下則為練習版本,我們將從該版本開始本例的學習,首先使用Visual Studio 2008打開該解決方案。
2.在我們的解決方案中添加一個順序工作流庫的項目,項目名稱為“SequencerFlow”。
3.從工具箱中拖拽一個Sequence活動到Visual Studio的工作流視圖設計器上。
4.然後,從工具箱中拖拽一個Code活動到我們剛添加的Sequence活動中。
5.在該活動的ExecuteCode屬性中輸入“DoTaskOne”,然後按下回車鍵。
6.Visual Studio會自動把我們帶到代碼編輯狀態。定位到Visual Studio剛剛添加的DoTaskOne方法,然後再該方法內輸入下面的代碼:
Console.WriteLine("Executing Task One...");
7.反復執行步驟4、5、6兩次,添加方法“DoTaskTwo”和“DoTaskThree”,然後在這些方法中修改Console.WriteLine輸出的內容(“One”依次改為“Two”、“Three”)。該工作流的視圖設計器現如下圖所示:
8.回到主應用程序,打開Program.cs文件,定位到Main方法上。在該方法中,找到下面的代碼:
Console.WriteLine("Waiting for workflow completion.");
9.在你找到的這行代碼下添加下面的代碼:
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(SequencerFlow.Workflow1));
instance.Start();
10.當然,我們需要在主應用程序項目中引用該SequencerFlow工作流庫。
11.編譯該應用程序,糾正任何出現的錯誤。按下F5或Ctrl+F5運行該應用程序。設置一個斷點或從命令提示符下運行該程序,這樣你就能看到輸出結果,結果如下:
正如你從步驟11看到的,該任務和我們所期望的順序依次被執行。需記住兩方面:Sequence活動是一個組合活動(其它活動的容器),其次就是它容納的活動以順序依次執行的。
使用Code活動
迄今為止,在本書中我們經常使用的另一個活動是Code活動。Code活動就是要讓你的工作流執行你所提供的自定義代碼。在下一章我們將看到,還有一種方法可調用外部方法。
當你把一個Code活動放入你的工作流中時,它的ExecuteCode屬性會被設置為工作流運行時將調用的方法的名稱。
實際上,當你在剛剛完成的Sequencer應用程序設置ExecuteCode屬性時,假如你仔細看看Visual Studio為你插入的代碼,它雖不是一個被調用的方法,但也差不多,它其實是一個事件處理,下面是我們插入代碼後的DoTaskOne方法:
private void DoTaskOne(object sender,EventArgs e)
{
Console.WriteLine("Executing Task One");
}
正如你看到的,當工作流運行時在執行你的Code活動時,它會觸發一個事件,該事件的名稱就是你在ExecuteCode屬性中設置的值。我們將在本書的剩余部分中好好利用這個Code活動。
使用Throw活動
在本書中很早以前就提到過該活動,但我還沒有真正加深這個基本的工作流處理模型的概念。因此,我們需能對真實世界中的各種各樣的情況進行建模,這其中就包含我們需要拋出一個異常的情況。假設有些事情在前進的道路上並不平坦,我們的軟件並不能為防止拋出異常而處理任何其它任何情況。假如我們樂意的話,我們可使用C#中的throw關鍵字來拋出一個異常,但在工作流中,我們使用一個特別的活動來做這些事,並使用一個特別的活動來處理這些異常,這些我們將在下節看到。假如我們使用C#中的throw關鍵字的話,工作流運行時會“swallows(淹沒)”該異常,並不會給出通知信息。
這種現象的原因是Throw活動。當工作流運行時遭遇Throw活動時,假如沒有相關的失敗處理操作,工作流運行時將觸發WorkflowTerminated事件。但請記住,屆時,工作流實例會被終止,工作流運行時會被停止。在這時任何更正異常狀況的任何嘗試都已為時已晚,我們僅能做的是重啟工作流並開始一個新的工作流實例。假如我們想在終止前更早地處理異常,我們需要使用Throw和FaultHandler活動組合。
備注:推薦的練習使用了Throw和FaultHandler組合而不是單一的Throw。使用Throw活動本身等同於在傳統應用程序代碼中使用沒有進行異常處理的C#的throw關鍵字。在本節,我們將單獨使用Throw來看看會發生什麼。在下一節,我們將使用Throw和FaultHandler活動組合來看看他們怎樣協同工作。
回到我們關注的Throw活動,當你拖拽一個Throw活動到設計器上後,你可找到兩個需進行設置的屬性。首先是FaultType屬性,該屬性告知Throw活動將拋出什麼類型的異常;另一個是Fault屬性,假如此時Throw活動拋出的異常不為空,它就指示該Throw活動引發的異常對象。
FaultType屬性不必做大量的解釋,它簡單地告知工作流實例將拋出的異常類型。我們沒有指明的異常由工作流運行時進行處理或者被忽略(假如你想處理的話,也可在以後進行處理)。
但Fault屬性的背後有什麼密碼呢?假如設置了該屬性的話,它才真正會是Throw活動所使用異常。假如該屬性為空的話,Throw活動仍舊拋出一個FaultType指定的類型的異常,但它是一個新的異常,該異常沒有既定的消息(記住,Message屬性為我們提供了一些除它本身的異常類型之外的關於錯誤的一些描述)。
假如你想讓Throw活動拋出一個帶有詳細Message的異常,你需要使用new操作符創建該異常的一個實例並把它指定到你綁定的Throw活動的相同屬性上。
我再以略微不同的語言來表達上述這些。Throw活動,更具體地說,它的Fault屬性會和你的工作流中所選擇的活動(包括root活動)中的具有同一種異常類型的一個屬性綁定到一起。就是說,假如你有一個拋出類型異常為NullReferenceException的Throw活動,你就必須在你的工作流中的一些活動上提供一個類型為NullReferenceException的屬性以讓Throw活動使用。然後讓Throw活動綁定這些活動的屬性,以便它能使用你用new操作符產生的同一個異常。
在這裡,我們會寫一些代碼來進行試驗。我們就開始創建一個使用了Throw活動的小工作流來看看它是怎樣工作的。
創建一個使用了Throw活動的工作流
1.在下載的本章的源代碼內有兩個名稱為“ErrorThrower Completed”和“ErrorThrower”的文件夾,“ErrorThrower Completed”文件夾內為本例的完整代碼,“ErrorThrower”文件夾內為本例的練習項目。我們現在就使用Visual Studio打開“ErrorThrower”文件夾內的項目。
2.打開ErrorThrower後,向該解決方案添加一個順序工作流庫的項目,名稱為ErrorFlow。
3.從工具箱中拖拽一個Code活動到Visual Studio工作流視圖設計器上,設置該Code活動的ExecuteCode屬性的值為“PreThrow”。
4.然後,從工具箱中拖拽一個Throw活動到設計器上,位置在上一步添加的Code活動的下面。
5.在Throw活動的屬性面板上,選中它的FaultType屬性,然後點擊浏覽(...)按鈕。(以三個點作為text的按鈕通常表示浏覽。)
6.這將出現一個“浏覽並選擇.NET類型”對話框。在裡面,選擇Throw活動將構建的異常類型。我們輸入或選擇“System.Exception”,然後點擊確定。
7.選中Throw活動的Fault屬性,然後點擊它浏覽(...)按鈕。
備注:未設置Fault屬性,甚至未設置FaultType屬性都不會導致編譯失敗。但是,這將設置一個message內容為“Fault屬性未設置”的System.Exception類型的異常。
8.這將彈出“將Fault綁定到活動的屬性”對話框。因為我們還未添加fault代碼,因此我們點擊“綁定到新成員”選項卡,然後在“新成員名稱”中輸入WorkflowException,最後點擊確定。這就為你的root活動添加了WorkflowException屬性。
9.添加第二個Code活動,設置它的ExecuteCode屬性的值為“PostThrow”。此時的視圖設計器界面如下:
10.現在我們的工作流就創建好了,然後將添加相應的代碼。查看Workflow1.cs的代碼,找到前面步驟添加的PreThrow事件處理程序,添加下面的代碼:
Console.WriteLine("Pre-throwing the exception");
WorkflowException = new Exception(
"This exception thrown for test and evaluation purposes");
11.同樣,找到PostThrow事件處理程序並添加下面的代碼:
Console.WriteLine("Post-throwing the exception (You won't see this output!)");
12.工作流現在就設計完成了,我們現在回到主應用程序繼續工作。打開Program.cs文件,定位到Main方法。在Main方法內,找到下面的代碼:
// Print banner.
Console.WriteLine("Waiting for workflow completion.");
13.在上面的代碼下,添加下面的代碼:
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(ErrorFlow.Workflow1));
instance.Start();
14.現在,為主應用程序添加對ErrorFlow工作流庫的項目引用。
15.編譯應用程序,糾正任何編譯錯誤。按下F5或Ctrl+F5運行該應用程序。你將看到下面的結果:
假如你仔細看看輸出結果,你將看到WorkflowTermination事件處理會被調用,並為我們顯示了終止的原因:有一個異常。該異常的Message和我們在步驟10中添加的類型為Exception的WorkflowException異常的相關信息相匹配。
備注:當你像在步驟8中一樣添加新的屬性時,這些由Visual Studio插入的屬性就是依賴屬性(看看第四章)。
現在我們看了在WF中異常是怎樣構建的,我們又怎麼捕獲處理它們呢?畢竟,在工作流的終止事件處理程序中處理異常的話通常都太遲,對於我們沒有任何價值。還好,WF為我們提供了FaultHandler活動,我們現在就來看看。
使用FaultHandler活動
使用FaultHandler的方式和我們目前為止看到過的其它任何活動的使用方式有細小的差別。其實,我們更應該仔細地去看看視圖設計器。為什麼呢?因為相比其它的工作流活動,錯誤處理有一個單獨的設計界面(其實,還有第三個設計界面,它為取消處理服務,我們也將在此看到)。
備注:在第15章(“工作流和事務”)中,我們將看到補償活動,它包含補償事務。處理錯誤就是其中的一部分。補償的意思是,產生一個動作,以減輕異常可能帶來的危害。
快速浏覽工作流視圖設計器
在此時,假如你創建過我給出的工作流實例的話,你可能會對拖拽活動到工作流視圖設計器上,然後設置它們的屬性,然後編譯並執行基本工作流的代碼的這一系列的工作方式感到滿意。然而,我還有一些東西沒有告訴你,我保留它們的原因是因為我們此前的焦點是放在工作流程序的編寫和執行上。
但現在,你已經使用過工作流中的設計工具,體會過工作流的編寫,Visual Studio的使用。我們就花點時間來看看Visual Studio提供的在工作流輔助設計方面的其它東西,這主要有兩個,簡要的說就是:附加的視圖設計器界面和調試。
附加的視圖設計器界面
假如你回頭看看一至六章,你會看到那些我們從工具箱拖拽活動到Visual Studio為我們呈現的視圖設計器界面上的例子。但你注意到視圖設計器窗口右鍵快捷菜單中的“查看工作流”、“查看取消處理程序”、“查看錯誤處理程序”三個菜單項沒有?如下圖所示:
圖7-1 視圖設計器界面右鍵快捷菜單
“查看工作流”菜單激活目前本書中我們已經使用過的默認的工作流視圖編輯界面。“查看取消處理程序”激活到另一個工作流取消視圖,我們可在其中為“取消”處理程序書寫代碼(見圖7-2)。“查看錯誤處理程序”激活到工作流異常視圖(見圖7-3)。
圖7-2 工作流取消視圖設計器界面
圖7-3 工作流異常視圖設計器界面
你隨時可能需要通過一些活動名稱下面的智能標記來訪問附加設計界面,如圖7-4。但有些活動,像EventHandlingScope活動(第十章),你還可訪問到更多的界面。
圖7-4通過智能標記進行界面的切換
沒有什麼奇怪的,你拖拽到取消設計界面上的工作流活動在該工作流實例被取消時執行。這使你有機會在工作流實例真正停止執行前去執行一些清理或通知的任務。
錯誤處理設計界面可容納許多的錯誤處理。每一個錯誤處理可處理一種,也僅能處理一種異常類型。組合活動一般而言都包含錯誤處理,假如需要的話也允許子活動進行處理錯誤而不用把它們發送到父活動中。回頭看看圖7-3,你可看到外面圍著藍色圓圈的兩個箭頭按鈕。錯誤處理活動可拖到這兩個箭頭之間,這些箭頭允許你進行滾動以顯示屏幕之外的錯誤處理活動。箭頭按鈕下面的區域是另一個和活動相關聯的異常處理的工作流設計界面。通常是拖拽一個Code活動到這裡為你做些在錯誤情況下的清理工作或執行其它必要的處理。在更多地了解工作流的調試視圖設計界面後我們將對此做一些練習。
調試視圖設計器
你或許不知道你可在工作流的視圖設計器上設置斷點。其實,在你的工作流中你還能一個活動一個活動地單步執行(而不是在你的源代碼中一行一行地執行)。
在工作流視圖設計器上設置斷點的方法是,右鍵單擊要設置斷點的活動,然後在快捷菜單中依次選擇“斷點”、“插入斷點”,參見下圖:
圖7-5 使用工作流視圖設計器設置斷點
然後工作流視圖設計器會在該活動的圖形界面中放置一個紅色圓球,就像你在代碼中某一行上設置斷點進行調試時看到的紅色圓球一樣,參見7-6。你也能移除斷點,禁用所有斷點等等。
圖7-6 帶斷點的活動
有了這些知識儲備,我們現在就可向我們的工作流添加FaultHandler活動了。
修改我們的工作流,以便使用FaultHandler活動
1.在Visual Studio中打開ErrorThrower應用程序,選擇ErrorFlow項目,選中Workflow1.cs文件,點擊視圖設計器,激活到工作流視圖設計器界面上,我們需要修改些東西。
2.通過右鍵快捷菜單(“查看錯誤處理程序”)切換到工作流異常視圖設計器界面,如下圖:
3.從工具箱中選中FaultHandler活動,拖拽到工作流設計器的異常處理界面上,然後放到兩個藍色按鈕之間。現在你的視圖設計器顯示效果如下所示:
4.我們需設置一些屬性,以使錯誤處理全面投入運作。我們首先要設置的是FaultType屬性。在Visual Studio的屬性面板中選中FaultType屬性,點擊浏覽按鈕(該按鈕是三個點)激活“浏覽並選擇.NET類型”對話框。如下圖:
5.在“浏覽並選擇.NET類型”對話框打開後,選擇或輸入“System.Exception”類型名稱,點擊確定關閉對話框,你會發現FaultType屬性的值已被設置為“System.Exception”了。
備注:我們設置FaultHandler活動使用的異常類型和我們本章前面使用Throw活動時設置的異常類型是相同的,但這不是巧合。它們的設置要匹配。假如在你的工作流中沒有為一個Throw活動進行相應的異常處理,那請牢記,假如運行時拋出異常,你的工作流將很快就執行到WorkflowTerminated事件。假如你不想這樣,就應添加恰當(屬性)的FaultHandler活動。
備注:盡管在前面的圖片中我們看到了Fault屬性,但它實際上是禁用的,因此不能設置,我們忽略它。
6.迄今為止,我們添加了FaultHandler活動,並設置了它將處理的異常類型,但我們其實還未寫入任何代碼來處理可能拋出的異常。因此,我們要從工具箱中拖拽一個Code到我們添加的FaultHandler活動的下方區域中,該區域以“faultHandlerActivity1”命名,它看起來就像是一個微型的工作流視圖設計器。我們拖入的Code活動的ExecuteCode屬性設置為“OnException”,然後按下回車鍵。
7.然後Visual Studio將切換到代碼視圖中,找到Visual Studio剛添加的OnException事件處理並為其添加以下代碼:
Console.WriteLine("Exception handled within the workflow! The exception was: '{0}'",
WorkflowException != null ? WorkflowException.Message :
"Exception property not set, generic exception thrown");
8.現在編譯並執行代碼,你將看到下面的執行結果:
備注:在這種層次上拋出和處理異常,你的工作流實質上仍舊會被停止。這樣做的優點是你的工作流能帶著異常工作而不是把異常拋出給工作流運行時處理。假如你想在特定的異常拋出後還能繼續進行處理,你就不要用Throw活動和FaultHandler活動來處理它們。相反,你要使用try/catch來包圍活動代碼,以便異常絕不會傳給運行時處置。假如你不能充分地在(try/catch)內部處理異常,那只能求助於Throw活動和FaultHandler活動。
使用Suspend活動
另一個在特定條件下會對你有用的活動是Suspend活動。事實上,它常見的使用場景是使用FaultHandler活動處理錯誤後,再使用Suspend活動進行暫停,然後發送需要人為干預的信號。
使用Suspend活動時,你需要為該活動的Error屬性提供一個字符串。這個屬性可以綁定在一個依賴屬性上(這一點像Throw活動)、一個類的屬性或字段、甚至是一個文本字符串(本例中我們將這樣做)。當Suspend活動執行時,工作流運行時會觸發WorkflowSuspended事件,傳給該事件的argument參數中將帶有該error字符串。
使一個工作流實例處於暫停狀態的含義是,該實例當前不再執行,但它也不被卸載。本質上它維持這種形式,等待一些動作。它也不被認為是空閒狀態,因此自動的持久化對它也不起作用。使用Suspend活動相對簡單,下面你就將看到。
備注:在你的基於工作流的應用程序中處理WorkflowSuspended事件是一個好主意,這使工作流實例進入暫停狀態後為你提供一個動作。至少你能得到工作流實例已經被暫停了的通知,你可移除、恢復或者重新啟動這些工作流實例。
修改我們的工作流,以便使用Suspend活動
1.下載本章源代碼,在Visual Studio中打開ErrorSuspender應用程序,選中ErrorFlow項目中的Workflow.cs文件,打開該文件的工作流視圖設計器界面。選擇工作流異常的設計界面,我們將為System.Exception錯誤處理程序添加Suspend活動。
2.從工具箱中拖拽一個Suspend活動到錯誤處理程序界面上,並把它放到Code活動的下面,如下圖所示:
3.設置該Suspend活動的Error屬性為“This is an example suspension error...”
提示:我們在這裡輸入的是一個文本字符串,但是,你也能把它綁定到一個字符串類型的依賴屬性,這樣當你的工作流執行時可更容易地對它的值進行設置。方法是單擊浏覽按鈕(三個點的按鈕),打開“將Error綁定到活動的屬性”對話框,然後你就可對它要綁定的屬性進行選擇。
4.因為在我們的主應用程序中沒有WorkflowSuspended事件處理程序,因此我們需要對主應用程序的Program.cs文件進行編輯。在合適的位置添加下面的代碼:
workflowRuntime.WorkflowSuspended+=newEventHandler<WorkflowSuspendedEventArgs>(workflowSuspended);
5.因為我們使用了名稱為WorkflowSuspended的事件處理程序,因此我們需要實現該事件處理程序,代碼如下:
static void workflowSuspended(object sender, WorkflowSuspendedEventArgs e)
{
Console.WriteLine("Workflow instance suspended, error: '{0}'.", e.Error);
waitHandle.Set();
}
6.編譯該應用程序,然後按F5或Ctrl+F5執行該程序。該程序的輸出結果如下:
運行該應用程序時,你會在控制台中看到主應用程序中的WorkflowSuspended事件處理程序產生的輸出結果。但你能做更多的工作,而不是僅僅向控制台輸出一串文本。你能為你的業務處理工作流產生任何其它動作。盡管在這裡你可能恢復該工作流的處理過程,但通常並不建議這樣做。一是全部正處理的活動將會被跳過,保留你的工作流實例以進行處理過程的恢復是後面階段要做的事,這可能不是好事情(跳過了那些步驟,你又怎麼說明它的原因呢?)。但是至少,你能從處理進程中干淨地移除該工作流實例,並可使用任何必要的清理代碼。
似乎異常和暫停工作流實例都有不足,因此,假如你需要的話,你可這樣做,那就是終止你的工作流實例。讓我們來看看怎麼做。
使用Terminate活動
有些時候事情會變得很糟糕,例如你沒有資源,需要結束某個工作流實例;也許從外部進程中返回的一些數據的格式或者計算結果是錯誤的;或者數據庫服務器出現問題,沒有它你就不能前進等等。
WF為我們提供了一個現成的方式來終止我們的工作流,那就是使用Terminate活動。Terminate活動的使用方法和Suspend活動完全相同,事實上它們的屬性也是相同的。不同之處在於,當Terminate執行時,所有期望你的工作流實例要繼續執行的事情都將丟失。
當Terminate執行時,工作流運行時觸發WorkflowTerminated事件,這正像有一個未處理的異常一樣。當處理WorkflowTerminated事件時獲取兩個不同方面的信息是困難的,所有你能做的實際上就是檢查WorkflowTerminatedEventArgs參數,看看它的Exception屬性。假如該工作流實例是使用Terminate活動終止的,該異常類型將會是System.Workflow.ComponentModel.WorkflowTerminatedException而不會是其它(甚至是更加常見)的異常類型。
我們就來看看在我們的工作流代碼中怎樣使用Terminate活動。
修改我們的工作流,以便使用Terminate活動
1.下載本章源代碼,用Visual Studio打開ErrorTerminator文件夾中的解決方案(ErrorTerminator Completed文件夾中為本例的最終源代碼)。選中ErrorFlow項目中的Workflow1.cs文件,打開它的工作流視圖設計器界面。
2.在錯誤處理程序的設計界面上刪除已存在的Suspend活動,然後從工具箱中拖拽一個Terminate活動到錯誤處理程序的設計界面上,把該活動放在Code活動的下面。
3.在放好該Terminate活動後,設置它的Error屬性為“This is an example termination error...”字符串。
備注:再重復一遍,你可像我們現在做的一樣,設置該屬性為一個文本字符串,但你也能把該屬性綁定到某個活動的字段、屬性或者依賴屬性上。
4.編譯該應用程序,修正所有的編譯錯誤,然後按下F5或者Ctrl+F5運行該應用程序,你將看到下面的運行結果:
Terminate活動和Suspend活動一樣,都是相當簡單的活動,但它很強大。你通常不會需要它,但當你的工作流出現問題不能繼續運行時,Terminate活動就是工具箱中最好的工具。
本文配套源碼