程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WF從入門到精通(第三章):workflow實例

WF從入門到精通(第三章):workflow實例

編輯:關於.NET

學習完本章,你將掌握:

1.使用不帶參數和帶參數二種方式初始化一個workflow實例

2.測定你運行中的workflow實例的狀況

3.停止workflow實例

4.確定你的workflow空閒或終止的原因

一個workflow實例由一個或多個活動組成。(我們將在第七章開始介紹各種活動:“Basic Activity Operations.”)“primary activity”或者“root activity”被稱作“workflow definition”。“workflow definition”通常的行為是為其它將要工作的活動充當一個容器。

注:“workflow definition”是你要求workflow去執行的東西,而一個workflow實例是一個正在執行的“workflow definition”。它們之間有明顯的區別,一個正在執行當中,而另一個則不是。

workflow實例從哪裡來?它們當然應由你來創建。如你有困難來完成這個任務,並且自動創建的workflow符合你的應用要求的話,也可由軟件來完成,但至少你也要寫出workflow的任務或者workflow運行時將為你執行的任務。Microsoft提供了workflow運行時,你還得創建余下的東西。畢竟,這是你的應用。

WF在上述這些地方的創建上可以為你提供幫助,WF不僅將執行你創建的workflow實例,而且也將幫助你去創建它們。WF集成了豐富的圖形界面設計器,它能幫你以相同的方式把workflow集成到你創建的ASP.NET Web Forms、Windows Forms或者WPF應用中。你可在工具箱上滾動鼠標,從許多活動項中選中一個,然後把它拖到設計界面上並釋放它。假如這個活動項具有可配置的屬性,你還可使用Visual Studio中的屬性面板來配置它,使它符合你的意圖。我們已在第一章簡要地使用過workflow設計器,在這裡我們將再次使用它,畢竟與WF相關的工作幾乎全是創建workflow任務,workflow可視化設計器的使用是開發過程中巨大的一個組成部分。

workflow實例和任何其它軟件類似。它們會開始執行、運行,直到遇到終止條件時終止。這些或許是數據庫中的所有記錄已被處理,所有需被壓縮的檔案已被壓縮,或者workflow發向各個審批方的文檔已被批復(同意或不同意),或者是處理已經完成。它只有一個正常的啟動位置,但有一個或多個正常的可能停止的位置。

實例也能維持錯誤、異常。你可以處理這些異常也可不處理它。在某些情況下,或許你不想去處理出現的異常,並留到以後進行處理。

有時,一個workflow處理過程會執行很長很長時間才能完成。例如,一個處理過程發送了一份零件的訂單並等待訂單被接收。在workflow終止前須確認零件的型號和數目,而這或許會花去幾天,幾周甚至幾月。因此,難道一個workflow實例也需要在內存裡維持激活狀態幾天,幾周或者幾月嗎?假如服務器崩潰或電源斷電怎麼辦?你的workflow實例、數據、應用程序狀態不是通通丟失了嗎?  

workflow實例和組成實例的活動是workflow處理過程中的重要部分。WF已經為workflow實例的創建及執行提供了強大的支持。我們就來看看WorkflowInstance對象。

WorkflowInstance對象介紹

workflowInstance是一個WF對象,它為你提供了你的獨立的workflow任務上下文(環境)。你可使用這個對象去找到在你的處理任務中事情將是如何進行的。就像我們有方法和屬性去控制workflow運行時一樣,我們也有方法和屬性並用它們和我們的workflow實例進行交互。表3-1列出了大多數WorkflowInstance屬性,表3-2列出了經常使用的方法。我們還將在第五章看到一些額外的屬性和方法,“工作流跟蹤”。  表3-1 WorkflowInstance的屬性 屬性 功能 InstanceId 得到workflow實例的唯一標識(一個Guid) WorkflowRuntime 得到本workflow實例的WorkflowRuntime   表3-2 WorkflowInstance的方法 方法 功能 ApplyWorkflowChanges 通過WorkflowChanges對象申請對workflow實例進行更改。這允許你在workflow執行時修改它(增加、移出或更改活動),當動態的更改實施時,workflow實例會被暫停。 GetWorkflowDefinition 檢索本workflow實例的根(root)活動。 Resume 恢復執行先前被暫停的workflow實例。假如workflow實例並未處於暫停狀態,則不做任何事情。假如workflow實例處在暫停狀態,workflow運行時就會在workflow實例剛被恢復後觸發WorkflowResumed事件。 Start 啟動一個workflow實例的執行,在這個workflow實例根活動上調用ExecuteActivity。假如Start發生異常,它通過調用Terminate終止這個workflow實例,並附加異常相關信息作為終止的原因。 Suspend 同步暫停本workflow實例。假如workflow實例本就處於暫停狀態,則不做任何事情。假如workflow實例正在運行,則workflow運行時就暫停該實例,然後設置SuspendOrTerminateInfoProperty(說明原因)並進入Suspend,觸發WorkflowSuspended事件。 Terminate 同步終止本workflow實例。當宿主需要終止workflow實例時,workflow運行時就終止這個實例並試圖持久化實例的最終狀態。然後WorkflowInstance設置SuspendOrTerminateInfoProperty(說明原因)並進入Terminate。最後,它觸發WorkflowTerminated事件並把終止原因傳到WorkflowTerminateException中的Message屬性並包含到WorkflowTerminatedEventArgs事件參數中。另外,假如在持久化時發生異常,workflow運行時取而代之地就把異常傳到WorkflowTerminatedEventArgs事件參數中。

還有更多和WorkflowInstance相關的方法還未列出。到第六章“實例的加載和卸載”,我們持久化工作流到數據庫中時將看到他們的更多細節。

啟動一個工作流實例

當我們啟動一個workflow實例前,我們必須有一個workflow任務讓WF去執行。在第一章,我們通過Visual Studio為我們創建了一個基於workflow的項目,它自動包含一個workflow任務,我們對它進行了修改以進行U.S.和加拿大郵政編碼的驗證。如果需要的話,我們可以返回到那個項目去復制源代碼,或者引用PCodeFlow.exe程序集。然後我們就可直接使用這個已創建的workflow。實際上,你可以這麼去做。

然而,我們還是應該試著去學會寫workflow的應用。讓我們通過使用一個包含延時的順序工作流去模擬一個長時間運行的任務吧。我們將在延時前執行一些代碼,以彈出一個信息對話框。在經過延時後,我們將再次彈出一個信息對話框來指明我們的工作已經結束。通過本書的學習過程,我們的例子將會越來越詳細和豐富,但現在我們還處於入門階段,我們還將保持我們的例子並把注意力更多的放到概念上而不是提高技巧上。

注:記住,順序工作流執行活動時一個接著一個。這個處理方式可和狀態機工作流做下比較,狀態機工作流執行活動時是基於狀態的轉變。假如你現在對此一片茫然的話,不用擔心,我們將在下章進入該主題。

在WorkflowHost解決方案中添加一個順序工作流項目  

1.啟動Visual Studio 2008,加載上一章創建的名為“WorkflowHost”的解決方案准備進行編輯。

2.在解決方案中添加一個嶄新的workflow項目。

3.項目模板選擇順序工作流庫。

4.項目名稱起名為:LongRunningWorkflow。

現在打開workflow的視圖設計器准備創建我們的workflow任務。在視圖設計器中的大圖片中,我們將添加三個活動到這個新workflow任務中:兩個Code活動和一個Delay活動。Delay活動將被放到兩個Code活動中間,目的是可讓我們在Delay執行前和執行後都將彈出一個信息對話框。最初我們會指定一個合適的延時時間值,但稍後我們將對workflow任務進行修改,以使workflow任務初始化時能接受我們專門指定的一個延時時間值。

創建這個模擬需執行很長時間的順序工作流

1.激活workflow視圖設計器,移動鼠標到工具箱中。

2.從工具箱中選擇Code活動,並把該組件拖拽到workflow設計器的表面。

3.釋放鼠標並讓Code活動組件落到該順序工作流中。

4.就像在第一章一樣,我們將添加一些代碼到Code活動中以使worflow任務經過這個活動時執行。在此單擊Code活動以確保該活動的屬性面板已被激活。

5.在屬性面板中激活ExecuteCode屬性的下拉編輯框,它將允許你命名將被觸發的事件,該事件在Code活動中的代碼執行時觸發。

6.輸入“PreDelayMessage”。這樣就添加了一個事件到worflow代碼中。稍候,我們將修改這段代碼以顯示一個信息對話框。但現在我們仍繼續在workflow的視圖設計器上工作,因為我們需要添加另外兩個活動。

7.從工具箱中選擇Delay活動並添加到Code活動的下面。

注:順序活動,就像我們現在所做的工作一樣,是以順序的方式執行活動。順序由workflow視圖設計器中活動的位置決定。在workflow設計器窗口的頂部的活動首先執行,對於其它活動的執行順序則按到視圖設計器窗口底部的走向(箭頭)決定。在下章我們還將重溫這一過程。

8.我們需要為我們的Delay活動建立一個延時時間值。為此,我們要在Visual Studio屬性面板中改變TimeoutDuration的屬性。把最後兩個“00”改為“10”,意思是Delay活動將等待10秒鐘才允許workflow繼續下一步的處理。

9.現在我們需要添加第二個Code活動來顯示第二個信息對話框。為此,重復步驟2和步驟6添加一個新的Code活動,但設置ExecuteCode的屬性為“PostDelayMessage"來作為事件的命名。以下是在workflow視圖設計器中展示的workflow的最終結果:

我們還剩下兩個任務未完成。最終,我們需要把我們的workflow程序集引入到我們的主應用程序中以便執行它。但首先,我們必須添加必要的代碼以顯示那兩個信息對話框。我們已經在我們的workflow代碼創建了兩個事件:PreDelayMessage和PostDelayMessage。我們將為它們添加事件處理代碼,在裡面實際上就是彈出信息對話框的代碼。

為延時前和延時後的事件添加代碼

1.單擊LongRunningWorkflow項目中的Workflow1.cs文件,查看其代碼。

2.添加“System.Windows.Forms"的引用,並在Workflow1.cs文件聲明以下名稱空間:

using System.Windows.Forms;

3.定位到新插入的PreDelayMessage方法,在方法中插入以下代碼:MessageBox.Show("正在執行延時前的代碼。");

4.和上一步類似,定位到新插入的PostDelayMessage方法,在方法中插入以下代碼:MessageBox.Show("正在執行延時後的代碼。");

假如你這時編譯這個解決方案,那沒有任何錯誤,但是WorkflowHost應用程序仍舊會像先前一章一樣掛起。為什麼呢?因為盡管我們創建了一個我們能夠使用的workflow程序集,但我們並未請求主應用程序去執行它。WorkflowCompleted事件從未被觸發,因此自動重置事件也就不會釋放應用程序主線程。

為執行我們的workflow任務,我們要引用我們新創建的workflow程序集並添加代碼,以使WorkflowRuntime對象來揭開workflow任務工作的序幕。我們現在就開始吧。

宿主一個自定義workflow程序集並啟動一個不帶參數的workflow實例

1.首先在項目WorkflowHost中添加對項目LongRunningWorkflow的引用。

2.假如我們現在去編譯這個應用程序,WorkflowHost將編譯失敗。為什麼呢?原因是在前一章我們創建WorkflowHost項目時,我們僅僅添加了必須的引用以支持當時的環境編譯通過。但現在,我們添加了一個“System.Workflow.Runtime”引用。同時又引入了一個現成的workflow程序集到我們的宿主應用程序中,因此我們需要為WorkflowHost項目添加更多的和workflow相關的引用。我們需要添加的引用有“System.Workflow.Activities”和“System.Workflow.ComponentModel”。

3.打開Program.cs文件並定位到Main方法內的下面代碼上:Console.WriteLine("等待workflow完成。");

4.在上述代碼下面添加以下代碼:

WorkflowInstanceinstance=workflowRuntime.CreateWorkflow(typeof(LongRunningWorkflow.Workflow1));
instance.Start();

5.編譯並執行WorkflowHost應用程序。

執行結果如下:

讓我們回到下面非常關鍵的代碼上:

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(LongRunningWorkflow.Workflow1));
instance.Start();

在這裡,我們使用了WorkflowRuntime對象的CreateWorkflow方法創建了一個我們想去執行的workflow實例。當我們得到了一個返回的WorkflowInstance對象後,我們調用了它的Start方法來初始化workflow。注意這個workflow實例不需要我們預先輸入參數就能執行。如果能預先輸入一個可變的延時值那該多好?下一節我們將討論這一話題。

啟動一個帶參數的workflow實例

帶輸入參數啟動的Workflow實例把接收到的參數和相關的公有屬性對應起來。就是說,為傳入一個可變的延時值,我們只需在我們的workflow實例上創建一個公有的名為“Delay”屬性,並在創建這個實例時提供延時值即可。假如你對XML序列化和.NET中的“XmlSerializer”熟悉的話,創建一個workflow實例的過程就和把XML流反序列化成一個.NET對象的過程相似。事實上,這幾乎差不多。

期望被傳入workflow實例的參數值通常存儲在一個Dictionary對象的Values中,Dictionary對象的關鍵字使用string類型,對應的值使用簡單的Object對象。典型的代碼如下:

Dictionary<string,object> parms = new Dictionary<string,object>();

然後你可使用Dictionary對象的Add方法添加參數。關鍵字必須是一個string,它表示workflow的root活動所暴露的公有屬性的名稱。另外,對應的值的類型必須和活動的屬性類型一致。例如,我們傳入一個整形類型的延時值並且我們的workflow實例中暴露了一個名為Delay的屬性和其對應,那添加一個參數到Dictionary中的代碼就應像下面的一樣:

parms.Add("Delay",10); //延時10秒。

我們再次來寫一些代碼吧,我們相對做些小的修改,但這會獲得許多功能。我們以控制台命令行的方式接收我們輸入的一個整形數值作為延時值。為使我們的程序不會永遠運行下去,我們會把這個值限制在0到120之間,意思延時范圍從0秒到兩分鐘之間。我們也將對workflow增加Delay屬性。讓我們一起來對我們的workflow組件做第一次修正。為workflow添加一個輸入屬性

1.打開Workflow1.cs文件准備編輯。

2.在Workflow1的構造函數後,添加以下代碼:

private Int32 _delay = 10;
public Int32 Delay
{
  get { return _delay; }
  set
  {
    if (value < 0 || value > 120)
      value = 10;
    if (ExecutionStatus == ActivityExecutionStatus.Initialized)
    {
      _delay = value;
      delayActivity1.TimeoutDuration = new TimeSpan(0, 0, _delay);
    }
  }
}

我們對傳入的整形值進行了檢查,假如它超出范圍,我們就指定一個默認值。我們檢查了workflow是否處在即將執行狀態(不是已執行狀態)。這可防止有人在我們的workflow運行中對延時值進行修改。我們也需要對Main方法進行少量修改。我們需在命令行中輸入一個參數作為延時值。假如它不是一個整形值,我們就退出。否則,我們就接受它。假如它超過范圍(0到120秒),我們就對它進行必要的約束(為默認值10秒)。對Main所做的修改步驟如下:

啟動一個帶參數的workflow實例

1.打開Progrom.cs文件准備編輯。

2.定位到下面的代碼上:

workflowRuntime.WorkflowIdled += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowIdled);
workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);

3.在上述代碼後添加下面的代碼:

Int32 delay = 0;string val = args.Length > 0 ? args[0] : "10";
if (!Int32.TryParse(val, out delay))
{
  Console.WriteLine("你必須輸入一個整形值!");
  return;
}
  Dictionary<string, object> parms = new Dictionary<string, object>();

4.找到下面的代碼:

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(LongRunningWorkflow.Workflow1));

5.把上述代碼改為:

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(LongRunningWorkflow.Workflow1), parms);

編譯並像在第一章那樣運行試試,通過輸入不同數值的延時時間,就可看到彈出的兩個信息對話框所反映出的延時效果。

確定Workflow實例的狀態

有趣的是,假如你看看workflow運行時對象及workflow實例對象的方法和屬性,你找不到和狀態相關的屬性。你怎麼知道是否有一個workflow在執行呢?假如有一個,它處在那個狀態呢?空閒嗎?正執行當中嗎?我們怎麼確定?  

我將向前跳一小段,這其中大部分邏輯都放在workflow狀態的確定上。一個給定的workflow實例的workflow definition為您提供workflow的執行狀態。基類Activity暴露了一個ExecutionStatus屬性,它是一個ActivityExecutionStatus枚舉的一個成員。下表3-3列出了ActivityExecutionStatus的枚舉值和相關的意義。

表3-3 ActivityExecutionStatus枚舉值

屬性 功能 Canceling 活動正在取消中。 Closed 活動已被關閉。 Compensating 活動處於補償狀態。 Executing 活動當前正在運行。 Faulting 活動已產生並維持一個異常。 Initialized 活動已被初始化但還未運行。

表3-3中的所有枚舉值都涉及到一個活動對象,但你需記住workflow definition就是一個活動。這意味著假如我們查詢workflow definition的狀態,我們就能有效地確定整個實例的狀態。下面的過程演示了我們怎樣添加相應代碼來查詢workflow definitely。

確定workflow實例執行狀態

1.打開WorkflowHost項目的Program.cs文件准備編輯。

2.找到Main方法並定位到下面的代碼上:

instance.Start();

3.為了讓我們看到workflow實例的狀態,我們直接查詢workflow definition的狀態並把結果輸出到控制台中顯示出來。在上一步中定位到的代碼下插入以下代碼:

Console.WriteLine("workflow處在:{0}狀態。",
      instance.GetWorkflowDefiniton().ExecutionStatus.ToString());

終止Workflow實例

假如你需要這樣做的話,你也能容易地終止一個workflow實例,方法是通過執行workflow實例對象的Terminate方法。假如你在你的應用中添加了WorkflowTerminated的事件處理,你就能從Exception的Message屬性獲取終止的原因。你將發現Exception被包裝到WorkflowTerminatedEventArgs中,並傳入到WorkflowTerminated的事件處理程序中。這些代碼WorkflowHost中已經包含了,我們還需添加一行代碼來結束workflow實例。

終止workflow實例

1.打開Program.cs文件,找到如下我們剛添加的代碼上:

Console.WriteLine("workflow處在:{0}狀態。",
      instance.GetWorkflowDefinition().ExecutionStatus.ToString();

2.在上述代碼下添加以下代碼:

instance.Terminate("用戶取消");

假如你現在編譯並運行WorkflowHost程序,為他提供一個25秒的延時值,你不會再看到任何一個信息對話框,控制台的輸出結果如下:

Dehydration和Rehydration

在我們離開workflow實例的這一話題之前,我想再談談“dehydrating”和“rehydrating”一個實例的概念。假如你有一個長時間運行的workflow任務或者有大量的任務執行,你就能卸載任務並把必須的執行環境信息存儲到一個SQL Server數據庫中,這要用到運行在WF之上的一個服務。

我們將在第六章詳細討論存儲的問題,我在這提及它是因為,對一件事來說,處理的目標是workflow實例。但另一方面,我們應聽聽這些術語,我不想讓你在深入此書後卻還不理解它們的基本意思。

當你“dehydrate”一個實例時,你就正在把它從執行狀態中移除並進行存儲以便以後恢復。典型的做法是使用WF的持久化服務,但你也能寫你自己的服務來做同樣的任務。以後當你的應用程序偵測到需重啟workflow實例時,你就“rehydrate”這個實例它就返回當時的執行狀態。這樣做的原因有很多,所有這些本書稍後都會簡要說明。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved