學習完本章,你將掌握:
1.理解在工作流環境中Parallel活動是怎樣執行的,並且懂得如何使用它們
2.並行執行路徑中的同步數據存取和臨界代碼區
3.使用ConditionedActivityGroup活動去執行根據條件表達式判斷執行路徑的並行活動
在本書中截止目前為止,我們僅僅處理過順序業務流程。如活動A執行後轉到活動B的執行等等。我們還沒看到過並行執行路徑和由此通常伴隨而來的錯綜復雜的情況。在本章中,我們將看看並行活動的處理過程,以及看看怎樣對橫跨並行執行路徑的共享信息進行同步存取。
使用Parallel活動
當你用完某樣東西需去雜貨店買的時候,通常都可能只有一條結帳流水線。所有的顧客都必須通過這條唯一的結帳線來付款。在那些罕見的情況下,當有兩個或更多的結帳線開放後,顧客和雜貨結帳的速度會更快,因為他們是以並行的方式結帳的。
在某種意義上,工作流活動也是如此。有時,你不能以混亂的方式甚至更糟糕的隨機的順序來執行特定的活動。在這些情況下,你必須選擇一個Sequence活動來容納你的工作流。但在其它時候,你可能需要設計在同一時間(或者如我們將看到的,幾乎是在同一時間)能夠執行多個處理過程的流程。對於這些情況,Parallel活動是一個選擇。
Parallel活動是一個組合活動,但它只支持Sequence活動作為它的子活動。(當然,你可自由地把你想使用的任何活動放到該Sequence活動中。)它至少需要兩個Sequence活動。
子Sequence活動並沒有在單獨的線程上執行,因此Parallel活動不是一個多線程活動,相反,那些子Sequence活動在單一的一個線程上執行。WF會只對一個Parallel活動執行路徑中的某一單獨的活動進行處理,直到該活動完成才會切換到另一個並行執行路徑中的一個單獨的活動。也就是說,在某個分支內的某個單獨的子活動完成後,才能安排其它分支中的另一個單獨的子活動去執行(譯者注:每個單獨的子活動是Parallel活動執行的最小單位)。各個並行活動間真正的執行順序是無法保證的。
這樣的結果是並行執行路徑不會同時被執行,因此它們並不是真正意義上的多線程中的並行執行。但是,它們執行時就像是在並行操作,並且你也可這樣看待它們。把Parallel活動看成是真正意義的並行過程是最明智的:你可像對待任何多線程下的處理過程來對待並行活動。
注意:假如你需要強制指定並行執行路徑間的順序,可考慮使用SynchronizationScope活動。本章晚些時候我將對它進行演示。
在此時有個值得關注的問題:“有Delay活動會怎麼樣呢?”正如你知道的,順序工作流中的Delay活動會停止執行指定的TimeoutDuration時間間隔。那這會停止Parallel活動的處理過程嗎?答案是不會。延時會導致特定的順序工作流路徑將被停止,但其它並行路徑會正常地繼續進行處理。
考慮到我所發出過的所有這些多線程警告,你或許會認為使用Parallel活動是一個挑戰。事實上,它非常容易使用。它在工作流視圖設計器中呈現出來的樣式和Listen活動(該活動在第10章“事件活動”中討論過)非常相像。和EventDriven活動不同的是,你將會在它裡面找到Sequence活動,除此之外,表現出來的可視化界面都是相似的。我們這就創建一個簡單的例子來演示一下Parallel活動。
創建一個帶有並行執行過程的新工作流應用程序
1.為了快速地演示Parallel活動,本例子使用的是一個基於控制台的Windows應用程序。我們需要下載本章源代碼,源代碼中包含有練習版和完整版兩個版本的解決方案項目,完整版中的項目可直接運行查看運行結果,我們在此使用Visual Studio打開練習版中的解決方案項目文件。
2.在Visual Studio打開了ParallelHelloWorld解決方案後,找到Workflow1工作流並在工作流視圖設計器中打開它。
3.從工具箱中拖拽一個Parallel活動到設計器界面上。
4.在Parallel活動放到工作流視圖設計器界面上後,它會自動地包含一對Sequence活動。在左邊分支的Sequence活動中拖入一個Code活動,在屬性面板上指定它的名稱為msg1,並在它的ExecuteCode屬性中輸入Message1。
備注:盡管在Parallel活動中的並行執行路徑不能少於兩個,但它並不會阻止你添加更多的並行執行路徑。假如你需要三個或更多的並行執行路徑,你可簡單地把多個Sequence活動拖拽到設計器界面上並把它們放到Parallel活動中。
5.在Visual Studio中切換到代碼視圖界面下,在Message1事件處理程序中添加下面的代碼,然後回到工作流視圖設計器界面上來。
Console.WriteLine("Hello,");
6.拖拽第二個Code活動到左邊的Sequence活動中,把它放到Code活動msg1的下面。該Code活動的名稱命名為msg2,並在它的ExecuteCode屬性中輸入Message2。
7.當Visual Studio為你切換到代碼視圖界面後,在Message2事件處理程序中輸入下面的代碼,然後回到工作流視圖設計器界面上來。
Console.WriteLine(" World!");
8.現在拖拽第三個Code活動到右邊的Sequence活動中。它的名稱命名為msg3,在它的ExecuteCode屬性中輸入Message3。
9.在Message3的事件處理程序中添加下面的代碼:
Console.WriteLine("The quick brown fox");
10.回到工作流視圖設計器界面上來,拖拽第四個Code活動並把它放進右邊的Sequence活動中,具體位置是在你前一步所添加的Code活動的下面。它的名稱命名為msg4,它的ExecuteCode屬性的值設置為Message4。
11.在Message4的事件處理程序中輸入下面的代碼:
Console.WriteLine(" jumps over the lazy dog.");
12.現在工作流的設計工作就完成了,你需要在ParallelHelloWorld應用程序中添加對ParallelFlow項目的項目級引用。
13.在ParallelHelloWorld項目中打開Program.cs文件,找到下面的一行代碼:
Console.WriteLine("Waiting for workflow completion.");
14.在你找到的上述代碼下,添加下面的代碼以便創建工作流實例:
// Create the workflow instance.
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(ParallelFlow.Workflow1));
// Start the workflow instance.
instance.Start();
15.編譯本解決方案,修正所有的編譯錯誤。
16.按下F5或者Ctrl+F5運行本應用程序。注意,為了能看到輸出結果,你應該在Program.cs中(在Main方法內)設置一個斷點。
和你在上面的圖片中看到的一樣,輸出的信息是雜亂的。假如左邊Sequence活動無干擾地一直運行到結束,則在輸出的結果中,“World!”就在“Hello,”的下面。假如右邊的Sequence活動沒有被干擾,則會輸出下面由Western Union發明的包含了所有26個字母,用來測試電傳打字機是否正常的一句話:“The quick brown fox jumps over the lazy dog”。但是,這是好事,因為從這些Code活動中輸出信息的雜亂順序正好指出了並行活動的執行方式。
假如你看仔細些,你將會看到各個Code活動都要一直運行到完成後才會轉去執行另一個Code活動。你或許也會注意到左邊的Sequence活動在右邊的Sequence活動前面啟動。當前該Parallel活動的執行順序和偽隨機數的生成結果是類似的。假如你使用相同的隨機種子值,產生的隨機數實際上並不是隨機的,它們是以可預期的方式生成的,在本Parallel活動中,也總是以這樣的方式來執行的,從左到右,從上到下。它的執行順序也是可預期的。
但是,不要把這樣的現象和你的業務邏輯聯系起來。就如我前面談到的,你應當認為Parallel活動的執行方式是真正並行的。你必須假定並行執行路徑是以隨機的順序執行的,即使可能個別的活動總是在切換執行上下文前結束。假如WF打破該契約(規則),那並非為多線程操作所設計的活動內部的代碼也會被中斷,這可不是什麼好事。
這就自然而然地產生了一個問題:你怎麼協調並行執行路徑,為什麼要這樣做呢?這個問題問得非常好,這把我們帶入了下面的話題:同步。
使用SynchronizationScope活動
任何曾經寫過多線程應用程序的人都知道線程同步是一個很關鍵的話題。現代Windows操作系統使用任務調度程序來控制CPU上線程的執行,任務調度程序在任何時候都可移走一個正在執行的線程,假如你疏忽,甚至可能在一個關鍵操作過程中發生這種情況。
當你寫基於Windows的應用程序時,你有許多的多線程手段可以利用:如互斥、內核事件、臨界區、信號量等等。但最終有兩件事必須得到控制:一是臨界代碼的完成過程中不能進行線程的切換,還有就是共享內存的存取(例如包含有volatile信息的變量)。
備注:這裡使用volatile是有一定含義的。它意味著數據改變,對於任意的時間段都不能保證仍然還是某一特定值。
WF通過提供SynchronizationScope活動的使用來解決上面提到的兩種情況。和傳統的多線程編程所要做的工作相比,你不需要去使用許多不同的手段(也就是說,你不需要去理解每種手段所使用的場合及使用方法)。相反,這一個活動的作用就是處理上面提到的兩種情況:完成臨界代碼區的執行過程及volatile內存的存取。
當你把一個SynchronizationScope活動放到你的工作流中的時候,WF會保證在該執行上下文切換到其它的並行路徑以前,該組合活動(指SynchronizationScope活動)內部的所有活動都將全部運行完成。這意味著你能在SynchronizationScope內部訪問所有的volatile內存和完成臨界區代碼的執行。
SynchronizationScope使用的機制和互斥(mutex)相似。(事實上,它會作為一個臨界區或者加鎖執行,因為同步的范圍不會跨越應用程序域。)在傳統的多線程編程中,mutex是為互相排斥所提供的一個對象。在一定程度上,它就像是一個令牌或鑰匙。換句話說,當一個線程要求進行互斥,僅僅另一個線程並沒有使用該互斥對象時才允許它去訪問這個互斥對象。假如另一個線程正在使用這個互斥對象,第二個線程就會阻塞(等待),直到第一個線程已經完成了它的任務並且釋放了該互斥對象。
互斥對象通常只不過是“named”這樣一個字符串,你也可使用任何你喜歡的字符串。但是,多個線程互斥訪問同一個互斥體必須使用同一個字符串對象。
SynchronizationScope也有相似的特點,這通過它的SynchronizationHandles屬性來提供支持。SynchronizationHandles其實是一個字符串集合,它們中的每一個(字符串)的作用是和要進行同步處理的其它的SynchronizationScope對象建立關聯。假如你沒有為該屬性指定至少一個字符串,Visual Studio也不會為你報錯,但是SynchronizationScope不會工作。和使用互斥對象一樣,所有要進行同步的SynchronizationScope活動都必須使用相同的SynchronizationScope字符串。
在進入我們的例子之前,我們要回頭去看看前一個示例應用程序的輸出結果。看到了那些雜亂的信息沒有?我們把SynchronizationScope活動運用到前面的示例應用程序中,來迫使這些信息以一個恰當的順序輸出。說得更明白一點,就是我們在執行上下文環境進行切換前,強制讓臨界區內的代碼一直運行到完成,但我也將引入volatile內存去演示它的工作方式。
創建一個帶有同步化並行執行方式的新工作流應用程序
1.在本實例中,你將再次使用基於控制台的Windows應用程序,該應用程序和前一個實例非常相似。在你下載的本章源代碼中,打開SynchronizedHelloWorld文件夾內的解決方案。
2.在Visual Studio加載了該解決方案後,在工作流視圖設計器中打開SynchronizedFlow項目中的Workflow1工作流,拖拽一個Parallel活動到設計器界面上。
3.現在拖拽一個SynchronizationScope活動到設計器界面上,把它放到左邊的Sequence活動中。
4.設置你剛才在工作流中所添加的SynchronizationScope活動的SynchronizationHandles屬性為SyncLock。
備注:你在SynchronizationHandles屬性中輸入的文本字符串並不重要。重要的是所有要被同步的SynchronizationScope活動都要使用相同的文本字符串。
5.拖拽一個Code活動到SynchronizationScope活動中,在屬性面板上指定它的名稱為msg1,它的ExecuteCode屬性為Message1。
6.Visual Studio會為你切換到代碼視圖中。在Message1的事件處理程序中輸入下面的代碼:
_msg = "Hello,";
PrintMessage();
7.但是你同時還需要添加_msg字段和PrintMessage方法。在Workflow1源代碼中找到它的構造器,在它的構造器下面添加下面的代碼:
private string _msg = String.Empty;
private void PrintMessage()
{
// Print the message to the screen
Console.Write(_msg);
}
8.拖拽第二個Code活動到SynchronizationScope活動中,把它放到msg1活動的下面。它的名稱命名為msg2,它的ExecuteCode屬性設置為Message2。
9.當Visual Studio切換到代碼視圖後,在Message2事件處理程序中輸入下面的代碼:
_msg = " World!\n";
PrintMessage();
10.拖拽一個SynchronizationScope活動放到右邊的Sequence活動中。
11.為了讓這個SynchronizationScope活動和你在第4步中插入的SynchronizationScope活動進行同步,在當前這個SynchronizationScope活動的SynchronizationHandles屬性中輸入SyncLock。
12.現在拖拽一個Code活動放到你剛才插入的這個SynchronizationScope活動中。它的名稱命名為msg3,它的ExecuteCode屬性設置為Message3。
13.在Message3事件處理程序中插入下面的代碼:
_msg = "The quick brown fox";
PrintMessage();
14.拖拽第四個Code活動,把它放入右邊Sequence活動中的SynchronizationScope活動中。它的名稱命名為msg4,它的ExecuteCode屬性設置為Message4。
15.在Message4事件處理程序中插入下面的代碼:
_msg = " jumps over the lazy dog.\n";
PrintMessage();
16.工作流現在就完成了。從SynchronizedHelloWorld應用程序中添加對該工作流的項目級引用。
17.打開SynchronizedHelloWorld項目中的Program.cs文件,找到下面一行代碼:
Console.WriteLine("Waiting for workflow completion.");
18.在你找到的上面一行代碼的下面,添加下面的代碼來創建一個工作流實例:
// Create the workflow instance.
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(SynchronizedFlow.Workflow1));
// Start the workflow instance.
instance.Start();
19.編譯該解決方案,糾正任何編譯錯誤。
20.執行該應用程序。你可能需要在Main方法中設置一個斷點才能看到輸出結果。假如輸出結果中顯示的這兩條信息還是雜亂的,你需要確認你在兩個SynchronizationScope活動(步驟4和步驟11中添加)的SynchronizationHandles屬性中輸入的是完全相同的字符串。
我們在本章中將介紹的最後一個活動和你看過或將看到的任何其它活動都大不一樣,它叫做ConditionedActivityGroup活動。它具有並行和循環兩方面的特征。讓我們瞧瞧吧。
使用ConditionedActivityGroup(CAG)活動
簡要地說,CondtionedActivityGroup活動(通常都稱作CAG)是一個組合活動,它為你提供了一個角色,使你能對要執行的並行子活動進行調度。總的來看,它會運行到你指定的一個條件為true時為止,假如你沒有指定這個條件,它則會運行到所有的子活動都報告它們沒有更多要去執行的任務時為止。我提到的這個條件就是CAG的until condition。
子活動並行執行,並且只有條件滿足的子活動才被執行。這個條件也就是所謂的when condition。假如沒有任何子活動滿足when條件,也就沒有子活動去執行,並且CAG活動會結束,除非你通過設置它的until條件強制它繼續進行。假如一個或多個子活動滿足了when條件,那這些子活動將並行執行。其它沒有滿足when條件的子活動將維持一個空閒狀態。通過設定要執行的子活動的when條件,你能決定哪些子活動將去執行。
CAG開始執行時要判斷它的until條件。假如判斷結果指明要繼續執行的話,則也要對每一個子活動的when條件進行判斷。判斷結果如確定要去執行,則會導致相關的子活動如期運行。假如超過一個活動要如期執行,則執行的順序將取決於它們被放入父CAG活動的順序。
在每一個子活動執行結束後,CAG將對until條件進行重新判定,同樣的還有子活動的when條件。這是因為對於一個正執行的活動來說,一旦它結束,就可能影響到其它子活動的執行順序,甚至是整個CAG活動。
CAG在工作流視圖設計器中的使用方式也和其它活動大不一樣。它的設計器用戶界面和其它活動的插入錯誤處理程序的界面相似(如圖11-1所示)。假如你拖拽各個子活動到CAG的設計器界面上,並把它們放到兩個箭頭圖標的中間,則在兩個箭頭中間的矩形略低的地方將顯示出如圖11-1所示的文字:編輯。當你拖拽子活動並放到這個矩形中後,在下方的窗口中將呈現出這些活動的圖形。在圖11-1中,你看到了一個名稱為setLevel1的活動,它來自於你即將創建的一個示例應用程序。
圖11-1ConditionedActivityGroup活動的設計器用戶界面
子活動拖進CAG中後有兩個顯示模式:預覽模式和編輯模式。圖11-1顯示的是編輯模式。在編輯模式中,你能為子活動設置屬性,例如設置它的when條件。當處於預覽模式時,你只能浏覽子活動的設計器圖片,這時在Visual Studio中顯示出的屬性都是CAG自己的。點擊圖11-1中的“編輯”文字左邊的小正方形按鈕,或者雙擊主CAG窗口中的子活動,你可在編輯和預覽模式之間進行切換。
放到CAG中的子活動都只能是單一的活動。假如在你的工作流處理過程中其中一個子活動需要執行超過一個以上的功能,就像處理一個事件的過程中,在事件響應時去執行一個Code活動一樣,你應使用一個Sequence活動來作為容器,把它放入CAG中作為CAG活動直接的子活動。
像CAG之類的活動的使用場合在什麼地方呢?我認為它是那些很少使用的活動中的一個,但當它適合你的處理模型時,它就會使事情變得非常的簡單。
例如,想象這樣一個工作流程,需要對某些化學制品或材料的量進行監控。假如你往儲備箱中填入了過量的這些東西,則工作流會自動的把這些過多的制品或物料釋放到一個溢出箱中。當儲備箱為空或者低於一個特定的下限值時,這個工作流會檢測到這個情況並發送一條警告信息。對於其它情況,工作流繼續對儲備箱中的量進行監控,但它不會產生任何動作。
把上面這些轉換成CAG後,CAG活動會一直運行到你決定不再需要進行監控時為止。假如儲備箱變得太空,一個子活動會發出一個警告,假如制品或物料的量超過了特定的上限值,則另一個子活動會打開溢出箱。你可使用一個包含了IfElse活動的while活動來實現同樣的功能,但在這個例子中CAG是最合適的。(也可以說使用CAG活動是更加簡潔的解決辦法。)
為了對CAG進行說明示范,我已創建好了我提到的這個應用程序。TankMonitor使用一個工作流來監控儲備箱中流體的量,它使用了一個簡單的動畫控件來模擬這個儲備箱。圖11-2展示了這個空儲備箱。
圖11-2儲備箱為空時的TankMonitor用戶界面
圖11-3展示的是儲備箱為半滿時的情形。而圖11-4展示出了儲備箱中流體過量時的情形。和你看到的一樣,儲備箱下面的label控件會為你提供任何狀態提示信息。這個label控件的提示信息完全由工作流的反饋結果控制,而不是被儲備箱的滑動條控件直接進行控制。
圖11-3儲備箱為半滿時的TankMonitor用戶界面
圖11-4儲備箱中流體過量時的TankMonitor用戶界面
在你的工作流中使用ConditionedActivityGroup活動
1.在下載的本章源代碼文件夾目錄中打開名稱為TankMonitor的練習項目解決方案。
2.在Visual Studio打開了該解決方案後,在工作流視圖設計器中打開Workflow1.cs文件。
3.拖拽一個ConditionedActivityGroup活動到工作流視圖設計器界面上。
4.下圖是conditionedActivityGroup1在Visual Studio的屬性窗口中呈現的界面。選擇UntilCondition屬性,這會顯示出一個向下的箭頭。點擊這個向下的箭頭,這會顯示出列表選擇項,此處選擇代碼條件。
5.通過點擊加號(+)展開UntilCondition屬性,在Condition屬性中輸入CheckContinue。在Visual Studio添加了對應的事件處理程序後,重新回到工作流視圖設計器上來。
6.生成該解決方案(按下F6),這將使本項目中的這個自定義活動呈現在工具箱中。現在,需向CAG中添加第一個子活動。從Visual Studio工具箱中,拖拽一個自定義SetLevel活動到工作流視圖設計器界面上,把它放到CAG活動的矩形區域中,矩形區域的具體位置在“<”按鈕的右邊。如下圖所示:
備注:在CAG的主窗口內顯示的鎖狀圖標表示子活動處於預覽模式。(CAG主窗口上方的文字也指明了當前所處的模式。)進入編輯模式後你能對setLevel1的屬性進行編輯,通過點擊“<”按鈕右方矩形窗口中的setLevel1活動的圖標,你也能進入編輯模式並對它的屬性進行編輯。
7.我們現在就先進入CAG的編輯模式,點擊“預覽”文字旁邊的微型正方形按鈕。這是一個開關型的按鈕,如再點擊一次這個按鈕,將使CAG再次進入預覽模式。
8.進入CAG的編輯模式後,你就可通過在CAG的主窗口中選中它的子活動,然後就可對它的屬性進行設置。我們現在點擊setLevel1活動以便激活它的屬性。
9.選中setLevel1的WhenCondition屬性,它將顯示一個向下的箭頭,然後從它的列表選擇項中選擇代碼條件。
10.展開這個WhenCondition屬性,在Condition屬性的文本框中輸入AlwaysExecute。Visual Studio也會自動為你添加一個對應的方法並會切換到代碼視圖下。我們需回到工作流視圖設計器界面下,因為你還需對setLevel1活動的多個屬性進行設置。
11.在setLevel1的Invoked屬性中輸入OnSetLevel並按下回車鍵。在Visual Studio添加了對應的OnSetLevel事件處理程序後,重新回到工作流視圖設計器界面下。
12.你需要為setLevel1設置的最後一個屬性是Level屬性。選擇它的Level屬性,這將顯示一個浏覽(...)按鈕,然後點擊這個浏覽按鈕。這將打開一個“將‘Level’綁定到活動的屬性”對話框。然後點擊綁定到新成員選項卡,點擊創建屬性,在新成員名稱中輸入TankLevel,最後點擊確定。
13.你現在需要向CAG中放入另外一個子活動。從工具箱中拖拽一個自定義Stop活動到“<”按鈕右邊的矩形窗口中,並把它放到setLevel1活動的右邊。
14.選擇stop1的WhenCondition屬性,這將顯示一個向下的箭頭,從它的列表選擇項中選擇代碼條件。
15.點擊WhenCondition旁邊的“+”,這將顯示出Condition屬性。在這個例子中,你可和setLevel1活動共享一個Condition屬性值,因此,選中這個Condition屬性,這將顯示一個下拉列表框,然後從它顯示出的列表項中選擇AlwaysExecute。
16.接下來選中stop1的Invoked屬性,在它的文本框中輸入OnStop。同樣,這也會添加一個OnStop方法。在添加了這個方法後重新回到工作流視圖設計器界面上來。
17.現在准備向CAG中添加第三個活動。這次,拖拽一個自定義UnderfillAlert活動到設計器界面上,把它放到CAG中stop1活動的右邊。
18.點擊underfillAlert1活動的WhenCondition屬性,從顯示出的下拉列表框中選擇代碼條件。
19.點擊“+”展開該WhenConditon屬性,在Condition中輸入CheckEmpty。同樣,在添加了對應的CheckEmpty方法後回到工作流視圖設計器界面上來。
20.接下來,你需要把underfillAlert1的level屬性綁定到你在第12步中創建的TankLevel屬性。為此,點擊level屬性激活它的浏覽(...)按鈕。然後點擊該浏覽按鈕,這就打開一個“將‘level’綁定到活動的屬性”對話框。但這次,你將綁定到一個現有的屬性,因此只需從現有的屬性中選中TankLevel即可,然後點擊確定。
21.underfillAlert1活動就完全配置好了,我們現在要把最後一個活動添加到本示例應用程序的CAG中。拖拽一個自定義OverfillRelease活動到設計器界面上,把它放到CAG中其它現有活動的最右邊。
22.同樣,你需要設置它的WhenCondition屬性,因此點擊overfillRelease1的WhenCondition屬性並從它的下拉列表中選擇代碼條件。
23.展開WhenCondition旁邊的“+”,這就顯示出它的Condition屬性。在該屬性中輸入CheckOverfill。在添加了對應的CheckOverfill方法後重新回到工作流視圖設計器界面上來。
24.和第20步中你為underfillAlert所做的工作一樣,現在把overfillRelease1的level屬性綁定到TankLevel屬性上。點擊level屬性,這就顯示出一個浏覽(...)按鈕。點擊該浏覽按鈕,這將打開一個“將‘level’綁定到活動的屬性”對話框。從現有屬性列表中選擇TankLevel,然後點擊確定。
25.本工作流在視圖設計器界面上的設計工作就完成了,現在需要切換到Workflow1.cs的代碼視圖下添加一些代碼。
26.打開Workflow1.cs源文件,在源文件中找到Workflow1構造器。在構造器下面添加如下的代碼,主要作用是創建在啟動工作流時需要用到的儲備箱中容納流體的下限值和上限值的屬性。
private bool _stop = false;
private Int32 _min = -1;
private Int32 _max = -1;
private bool _notificationIssued = false;
public Int32 TankMinimum
{
get { return _min; }
set { _min = value; }
}
public Int32 TankMaximum
{
get { return _max; }
set { _max = value; }
}
27.接下來你將看到的是CheckContinue方法,這個方法是你在設置CAG的UntilCondition屬性時自動添加的。這個方法實際上是一個事件處理程序,ConditionalEventArgs包含了一個Result屬性,你可對其設置以決定是讓CAG繼續進行處理還是讓它停止,如設置Result為true將使其停止,而設置為false將使其繼續進行處理。向CheckContinue中添加下面一行代碼(_stop是一個標志,它在OnStop事件處理程序中被設置):
e.Result = _stop;
28.兩個CAG活動,setLevel1和stop1,都應當始終運行。因此在AlwaysExecute中添加下面一行代碼:
e.Result = true;
29.找到OnSetLevel方法,在SetLevel事件被響應時將調用該方法。實際上儲備箱中的量是由WF自動為你設置的,因為你把setLevel1的Level屬性綁定到了TrankLevel這個依賴屬性上。下面添加的代碼的作用是對任何警告通知進行重置,以便在儲備箱中量的新值不再合理范圍內時能讓overfill活動或underfill活動發出它們的通知。
_notificationIssued = false;
30.Stop事件的作用是當它觸發時對CAG的UntilCondition進行設置以使它停止處理。在Workflow1.cs文件中找到OnStop方法,並為它添加下面兩行代碼:
// Set the stop flag
_stop = true;
31.接下來定位到underfillAlert1的WhenConditon屬性對應的CheckEmpty方法。盡管你想讓CAG每次都對它的子活動的WhenCondition進行判斷,但你並不想讓通知(低於下限值或超過上限值時)不停地發送到用戶界面上,因為這將消耗過多的CPU周期。實際上,你只想讓通知在儲備箱的容量狀態級別發生更改後只發出一次。下面的代碼就為你做這個工作,這些代碼添加到CheckEmpty方法中。
// If too empty, execute
e.Result = false;
if (TankLevel <= TankMinimum)
{
e.Result = !_notificationIssued;
_notificationIssued = e.Result;
} // if
32.overfillRelease1也需要對它的WhenCondition進行判斷,因此找到CheckOverfill方法並添加和上面相似的代碼:
// If too full, execute
e.Result = false;
if (TankLevel >= TankMaximum)
{
e.Result = !_notificationIssued;
_notificationIssued = e.Result;
} // if
33.保存所有打開的文件,編譯該解決方案。
34.執行該應用程序。向上或向下拉動滑動條可對儲備箱進行補充或抽空進行模擬。當儲備箱中的流體超過或低於邊界條件時注意觀察它下面所顯示的文本。
備注:我在創建這個TankMonitor應用程序的過程中,當添加事件處理程序時會感到它非常像是一個基於狀態機的工作流。假如在你創建順序工作流的過程中發現,在一個特別的業務流程中使用一點點基於狀態機的處理流程會更有用的話,CAG或許就是一個既快速又簡便的極好的選擇,這不用重新創建並調用一個獨立的基於狀態機的工作流。
信不信由你,到現在你已經看到過足夠多的用來解決許多和工作流任務相關的處理過程,但這些章節討論的話題還只是加深你對WF的理解。