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

WF從入門到精通(第六章):加載和卸載實例

編輯:關於.NET

學習完本章,你將掌握:

1.理解工作流實例為什麼要卸載和重新加載及其時機

2.理解工作流實例為什麼要持久化及其時機

3.搭建SQL Server 2005,使其為WF和工作流持久化提供支持

4.使用SqlWorkflowPersistenceService服務

5.在你的工作流代碼中進行實例的加載和卸載

6.使持久化服務能自動地加載工作流實例及卸載空閒中的工作流實例

假如你花點時間真正考慮該怎樣使用WF和工作流在你的應用程序中進行處理的話,你或許想像的許多解決方案中都包含那些需長時間運行的處理過程。畢竟,商業軟件本質上就是模擬和執行業務處理過程,這些許多的處理過程中都包含人或廠商、訂貨和發貨、計劃安排等等。人沒有在幾毫秒內自動進行處理的響應能力,但在已加載的企業服務器上則能做到點。服務器是寶貴、繁忙的資源,需讓它進行線程的輪轉,讓線程等待幾分鐘、幾小時甚至幾天、幾周是不能接受的,原因有很多。

因此WF的設計器必須提供一個機制,當等待一些長期運行的任務時,空閒工作流應暫時脫機。WF決定提供Microsoft SQL Server作為一個可選的存儲介質,因為數據庫是儲存(而不是失去)寶貴數據的好地方。WF也集成了另一個可插拔服務,我們可輕易地把它納入我們的工作流中以支持其持久化機制。怎麼做到這些、為什麼做、什麼時候做是我們在本章中將探討的問題。

持久化工作流實例

你知道現代Microsoft Windows操作系統本質上是一個非常特別的批處理軟件,它負責為各請求線程分配占用處理器的時間嗎?假如一個單一的線程獨占了處理器一個過份的時間周期,其它線程會“餓死”,系統將會死鎖。因此這個批處理軟件,也就是任務調度程序,要把線程從處理器的執行堆棧上移進和移除,以便所有的線程都能得到執行時間。

從某個意義上說,工作流也類似。假如你有許許多多長時間運行的工作流,它們都掛到某一特定的計算機上競爭處理時間和資源的話,那麼系統最終會阻塞未處理的工作流。這就沒有了可伸縮性。事實上,WF在維護它的工作流隊列時是非常高效的,但你可能對這些必須有一個物理上的上限表示贊同,把空閒、長期運行的工作流從激活執行狀態移除是一個好主意。

或者發生什麼意外,系統忽然被關閉呢?工作流完全在內存中處理,除非我們采取步驟持久化它們。因此,除非我們在突發事件發生之前有所准備,否則我們就將丟失了執行中的工作流實例。假如那些長期運行的工作流正管理著關鍵的進程,它們丟失了我們能承受得起嗎?在大多數情況下,我們都承受不起,至少我們不會心甘情願地允許這些進程在毫無防備措施的情況下就消失掉。

好消息是WF不僅為您提供了卸載工作流實例,然後又重新加載的方法,它也支持一個服務:SqlWorkflowPersistenceService,它用來把工作流實例序列化進SQL Server數據庫。假如你讀過前面一章,你可能已經熟悉甚至對把工作流實例的信息寫入數據庫的主張感到滿意。

因此,在什麼時侯卸載工作流實例,並且假如他們被卸載,我們應怎麼持久化它們呢?在執行中有一些非常特別的點可卸載工作流實例。在大多數情況下,發生這種情況是在我們會自動為剛才我之所以提到的——WF不能離開(或不願離開)長期運行的工作流程,在內存中不必要地消耗資源和處理時間的時候。但我們也能親自進行控制。這裡列出了工作流實例的卸載點,在那些地方可進行持久化。

1.在ActivityExecutionContext完成並結束(卸載)後。我們在第四章(活動類型和工作流類型介紹)簡要談過ActivityExecutionContext對象。

2.在Activity進入空閒狀態時。

3.一旦一個TransactionScopeActivity完成(卸載)時。我們將在第十五章(工作流和事務)看到這個活動。

4.一旦一個帶有PersistOnCloseAttribute屬性的Activity完成。

5.當你明確調用WorkflowInstance.Unload或WorkflowInstance.TryUnload時。

通過調用WorkflowInstance對象的特定方法或通過使用一個Delay活動讓你的工作流進入空閒狀態,你可控制工作流實例被持久化的時機。在延時的時候,通過傳遞一個參數到持久化服務的構造函數中,你將可對自動的持久化行為進行控制。

備注:暫停工作流實例和對工作流實例進行延時是不相同的。使用Delay活動將自動地把工作流實例寫入數據庫(前提是你正使用SqlWorkflowPersistenceService並已經對其進行配置,本章的最後一節將看到這些)。暫停僅僅是使工作流從激活的處理狀態中撤出。然而你也可選擇使用Unload或TryUnload以手動的方式把工作流寫入到數據庫中。

WF是如何完成這些的呢?這通過使用SqlWorkflowPersistenceService並結合創建一個特定的數據庫來完成這項任務(這非常像我們在前一章創建的跟蹤數據庫)。你可使用相關腳本來創建一個數據庫的架構(表和視圖)以及執行持久化所需的存儲過程。首先讓我們來創建數據庫。

搭建SQL Server以進行持久化

就像前一章一樣,我們通過在SQL Server Management Studio Express中創建一個新數據庫來開始我們的工作。

創建一個SQL Server 2005持久化數據庫

1.啟動SQL Server Management Studio,連接數據庫引擎。

2.在數據庫節點上單擊右鍵激活右鍵快捷菜單,選擇“新數據庫”。

3.在新數據庫對話框中輸入“WorkflowStore”作為數據庫的名稱字段,點擊確定。

4.下一步將執行WF為設置持久化所提供的腳本(這會創建表和視圖)。這些腳本的位置在<%WINDIR%>\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\ZH-CHS,在這裡<%WINDIR%>是指你的Windows目錄(通常是C:\Widows)。在SQL Server Management Studio打開SqlPersistence.Schema.sql文件。

5.SQL Server Management Studio會在一個新窗口中導入文件中的腳本,但在我們運行腳本前,我們需指明在哪個數據庫中運行這些腳本,因此我們要選擇WorkflowStore數據庫。

6.點擊工具欄上的執行按鈕執行這些腳本。

7.重復4-6步執行SqlPersistence.Logic.sql腳本。這將在數據庫中創建必須的存儲過程。

SqlWorkflowPersistenceService服務介紹

保存和恢復工作流實例是可選的:假如你不想(持久化)的話,你就可避免使用持久化存儲介質(如數據庫)來保存工作流實例。因此通過可插拔服務(SqlWorkflowPersistenceService)來實現持久化或許更有意義。當工作流實例正處於運行中的時侯,WorkflowInstance和SqlWorkflowPersistenceService協作工作以執行存儲和恢復任務。

表面上,所有這些聽起來相對地簡單。假如我們需要把工作流實例換出並存儲到數據庫,我們就通知持久化服務為我們存儲它。但假如我們使用單一的數據庫來持久化不同進程中運行的工作流會發生什麼呢?在工作流實例執行中是怎樣進行停止和重啟的呢?

使用單一的數據庫來存儲工作流實例並不罕見。但每個實例可能在不同的機器不同的進程中執行,假如要保存和稍後恢復工作流實例,我們也必須要有一個手段來存儲工作流在執行時刻實際的系統狀態。例如,SqlWorkflowPersistenceService會存儲實例是否被阻塞(等待其它東西),它的狀態(執行中,空閒等等)以及像序列化實例數據和擁有者標識等各種各樣的輔助信息。所有這些信息對於在以後的某個時間重現實例是必須的。

我們能夠通過WorkflowInstance對象的三個方法來控制上述的持久化,參看表6-1。

表6-1 WorkflowInstance方法

方法 功能 Load 加載先前被卸載(持久化)的工作流實例 TryUnload 試圖從內存中卸載(持久化)該工作流實例。和調用Unload不同的是,調用TryUnload時假如工作流實例不能立即被卸載,那它將不會被阻塞(維持執行狀態)。 Unload 從內存中卸載(持久化)該工作流實例。注意該方法為進行卸載將阻塞當前執行的線程,直到工作流實例被真正地卸載。這可以是一個漫長的操作,這取決於個人的工作流任務。

正如表6-1中所指出的,我們有兩個方法來用於卸載和持久化工作流實例。你該使用哪個方法取決於你的代碼想要做什麼事。Unload會暫停工作流實例來為其持久化做好准備。假如這要花費很長時間,該線程執行Unload操作也就要等待很長時間。然而,TryUnload在請求卸載一個執行中的實例時將立即返回,但這不能保證該工作流實例真正被卸載並持久化到數據庫中。為進行檢驗,你應檢查TryUnload方法的返回值。假如該值是true,該工作流實例本身就是卸載和持久化了的,假如該值是false,則該工作流實例還沒有被卸載和持久化。TryUnload的優點是你的線程不會處在等待狀態,當然缺點是你可能要對該執行中的工作流實例重復地使用TryUnload方法(進行檢查)。

卸載實例

盡管WF在特定的時間將卸載和持久化你的工作流實例,但有時候你可能想親自進行控制。對於這些情況,WorkflowInsance.Unload和WorkflowInstance.TryUnload是有助於你的。

假如你首先沒有插入SqlWorkflowPersistenceService就調用上述兩個方法中的任何一個的話,WF將拋出一個異常。當然,假如有某種形式的數據庫錯誤,你也將收到一個異常。因此,好的做法是使用try/catch塊來包圍這些調用,以阻止你的整個應用程序(發生異常時)崩潰。(注意這並不是說做任何事都與異常有關,有時你可能想要忽略它。)

我們來試試吧!我們先創建一個小圖形用戶界面,它為我們提供了一些按鈕,我們使用這些按鈕來迫使應用程序產生特定的行為。應用程序的復雜性會增加一點,但我們也將走向更加真實的應用中。

這裡我們創建的應用程序仍然相對簡單。它僅僅有幾個按鈕,我們在特定的時間點擊它們來迫使工作流實例卸載。(在下一節,我們將重新加載它。)我將故意強制一個長時間運行的工作流卸載,但和至今我們看到過的工作流不同,它將不會使用Delay活動。這樣做的原因就像你或許猜到的一樣簡單,是因為Delay活動很特別,它會使自己自動伴隨著持久化。相反,我會強制我們的工作流實例卸載而不是像Delay活動那樣自動進行卸載。在本章的“在空閒中加載和卸載實例”這一節我們將看到Delay活動和它們的作用。當前,我們將請求工作流線程休眠(sleep)10秒,以便為我們提供充足的時間來按下我們程序中的按鈕中的一個。

創建一個新的宿主應用程序

1.就像你在前一章做的一樣,打開Visual Studio創建一個新應用程序項目。但是,不是要創建一個基於控制台的應用程序,而是創建一個Windows應用程序,名稱為WorkflowPersister。下面的步驟在第二章中已經描述過:包含“添加工作流assembly引用”、“宿主工作流運行時”、“創建WorkflowRuntime工廠對象”,“啟動工作流運行時”,“停止工作流運行時”,“使用工作流運行時工廠對象”,“處理工作流運行時事件”過程。最後,添加一個app.config文件(可參考前一章中的“添加SqlTrackingService到你的工作流中”,可不要忘記添加System.Configuration的引用)。

2.現在向app.config文件中添加恰當的數據庫連接字符串(數據庫為WorkflowStore)。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <connectionStrings>
  <add name="StorageDatabase" connectionString="Data Source=(local)\SQLEXPRESS;Initial Catalog=WorkflowStore;Integrated Security=True;"/>
 </connectionStrings>
</configuration>

3.當你創建了WorkflowPersister項目時,Visual Studio顯示了Windows Forms視圖設計器。在Windows Forms視圖設計器中把鼠標移到工具箱上,選擇一個Button控件,並把它拖放到設計器的界面上。

4.我們將為這個按鈕設置一些富有意義的文字屬性,以便於我們知道我們點擊的是什麼。選中這個按鈕,然後在Visual Studio的屬性面板中選擇該按鈕的Text屬性,把該屬性的值設置為“Start Workflow”。

5.為該按鈕添加Click事件的處理程序,具體代碼將在後面的步驟中添加。

6.修改按鈕的位置和大小,如下圖所示:

7.重復步驟3至步驟5,再添加兩個按鈕,一個的text屬性為“Unload Workflow”,另一個的text屬性為“Load Workflow”。如下圖所示:

8.現在就為測試我們的工作流創建好了用戶界面,該是為我們將執行的應用程序添加事件處理代碼的時候了。當應用程序加載時我們需要初始化一些東西,做這些工作的一個很合適的地方是在主應用程序窗體中的Load事件處理程序。

9.在該事件處理程序(處理方法)中輸入下面的代碼:

_runtime = WorkflowFactory.GetWorkflowRuntime();
_runtime.WorkflowCompleted +=
  new EventHandler<WorkflowCompletedEventArgs>(Runtime_WorkflowCompleted);
_runtime.WorkflowTerminated +=
  new EventHandler<WorkflowTerminatedEventArgs>(Runtime_WorkflowTerminated);

10。在Form1類中聲明下面名稱為_runtime的字段:

protected WorkflowRuntime _runtime = null;
    protected WorkflowInstance _instance = null;

11.添加System.Workflow.Runtime、System.Workflow.ComponentModel和System.Workflow.Activity三個工作流組件的引用(可參考前面章節),然後在該代碼文件中添加下面的命名空間:

using System.Workflow.Runtime;

12.盡管我們現在有了一個應用程序來宿主工作流運行時,但它實際上沒做任何事。為完成些功能,我們需向按鈕的事件處理中添加一些代碼。先向button1_Click中添加下面的代碼:

button2.Enabled = true;
button1.Enabled = false;
_instance = _runtime.CreateWorkflow(typeof(PersistedWorkflow.Workflow1));
_instance.Start();

這些代碼使“Start Workflow”按鈕禁用,而讓“Unload Workflow”按鈕可用,然後啟動了一個新的工作流實例。

13.下一步,找到“Unload WorkflowInstance”按鈕的事件處理:button2_Click,然後添加下面的代碼。這裡,我們使用WorkflowInstance.Unload方法來卸載工作流實例並把它寫入數據庫。在工作流實例卸載後,我們讓“Load Workflow”按鈕可用。注意假如我們在卸載工作流實例時產生異常,“Load Workflow”按鈕是不可使用的。這樣做的意義是:假如卸載請求失敗,也就不用再加載。

button2.Enabled = false;
try
{
 _instance.Unload();
 button3.Enabled = true;
} // try
catch (Exception ex)
{
 MessageBox.Show(String.Format("Exception while unloading workflow" +
                " instance: '{0}'",ex.Message));
} // catch123

備注:牢記WorkflowInstance.Unload是同步的,雖然我在本章前面已經闡述過,但這點很重要。這意味著線程試圖卸載工作流實例時將會被阻塞(暫停),直到操作完成後為止(不管卸載實例時是成功還是失敗)。在這種情況下,可准確執行我想要的行為(指卸載),因為我不想反復查看實例是否被卸載。但有時,你想在卸載時不會被阻塞,就應使用前面說過的Workflowinstance.TryUnload。稍後,在你添加完最後一批代碼並運行應用程序時,當你單擊“Unload Workflow”時密切觀察,你會看到應用程序會簡短地被凍結,因為它正等待工作流卸載。

14.現在回到我們關注的工作流事件處理上:Runtime_WorkflowCompleted和Runtime_WorkflowTerminated。這兩個事件處理實際上完成相同的動作,那就是重置應用程序以便為另一個工作流實例的執行做好准備。在“button2”的“click”事件處理方法(該方法包含代碼我們已在前面的步驟中添加)的下面添加下面這些方法:

void Runtime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
WorkflowCompleted();
}
void Runtime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
{
WorkflowCompleted();
}

15.當然,我們現在還要創建“WorkflowCompleted”方法。假如你熟悉Windows編程,你可能知道Windows一直以來存在著的限制問題。這個限制簡單的說就是你不能在創建窗口控件的線程外的任何線程中改變該窗口控件的狀態。因此假如你想改變某個控件的text,你必須在創建該控件的同一線程上指定該控件的text,使用任何其它線程最有可能導致的結果是造成你的應用程序崩潰。因此我們即將加入的代碼對你來說可能很搞笑,但所有要做的這些的真正目的是確信我們是在原來的創建按鈕的線程中來使按鈕能用和禁用。(事件處理幾乎總是在不同的線程上進行調用。)假如我們只是在按鈕自身的事件處理中使其可用,應用程序可能還是能工作,但它更大的可能是崩潰和掛起。簡單地把下面的代碼復制並放到源文件Form1類的尾部,應用程序才會正確的工作。

private delegate void WorkflowCompletedDelegate();
private void WorkflowCompleted()
{
 if (this.InvokeRequired)
 {
  // Wrong thread, so switch to the UI thread
  WorkflowCompletedDelegate d = delegate() { WorkflowCompleted(); };
  this.Invoke(d);
 } // if
 else
 {
  button1.Enabled = true;
  button2.Enabled = false;
  button3.Enabled = false;
 } // else
}
  

16.在創建我們將執行的工作流之前我們需要最後做一件事,那就是要修改WorkflowFactory類。假如你從第五章(“為你的工作流添加跟蹤服務”)以來准確地遵循了所有的步驟來創建和修改WorkflowFactory的話,你實際上創建了一個為工作流運行時提供跟蹤服務的工廠對象。我們將對該代碼進行輕微的調整,把SqlTrackingService服務改為SqlWorkingPersistenceService,並且改變聲明命名空間的語句(把System.Workflow.Runtime.Tracking改為System.Workflow.Runtime.Hosting)。打開WorkflowFactory.cs文件進行編輯。

17.用下面的代碼來替換聲明的System.Workflow.Runtime.Tracking命名空間。

using System.Workflow.Runtime.Hosting;
    using System.Configuration;

18。最後為運行時添加持久化服務,方法是在創建工作流運行時對象後添加下面的代碼:

string conn = ConfigurationManager.ConnectionStrings["StorageDatabase"].ConnectionString;
_workflowRuntime.AddService(new SqlWorkflowPersistenceService(conn));

注意:因為我們在代碼中創建的工作流實例的類型是PersistedWorkflow.Workflow1類型(在第12步中),因此我們的宿主應用程序編譯並執行會出錯,我們將在下面的一節解決。

這裡我們就有了一個Windows圖形用戶界面和宿主應用程序,我們使用它們來承載我們的工作流。談到工作流,我們不是要創建並執行它嗎?其實,這在下面進行講解。

創建一個新的可卸載的工作流

1.像前面一章一樣,我們又將在我們的項目中創建一個新的順序工作流庫。在Visual Studio中激活WorkflowPersister應用程序,然後點擊“文件”菜單,選擇“添加”,當子菜單彈出後,選擇“新建項目”。從“添加新項目”的對話框中添加一個“順序工作流庫”的項目,項目名稱為“PersistedWorkflow”。

2.在應用程序解決方案中創建並添加一個新項目後,將呈現該工作流的視圖設計器界面。從工具箱中拖拽一個“Code”活動到設計器界面上。在Visual Studio屬性面板上設置“Code”活動的“ExecuteCode”屬性為PreUnload然後按下回車鍵。

3.然後Visual Studio將自動切換到該工作流的源代碼文件中,向剛剛插入的PreUnload方法添加下面的代碼:

_started = DateTime.Now;
System.Diagnostics.Trace.WriteLine(String.Format("*** Workflow {0} started: {1}",
                  WorkflowInstanceId.ToString(),
_started.ToString("MM/dd/yyyy hh:mm:ss.fff")));
System.Threading.Thread.Sleep(10000); // 10 seconds

4.為了計算工作流消耗的時間(下面的步驟中我們將看到,該時間至少在兩個Code活動執行的時間之間),我在一個名稱為“_started”字段中保存了啟動時間。在你的源文件中構造函數的上面添加該字段:

private DateTime _started = DateTime.MinValue;

5.現在切換到設計視圖,添加第二個Code活動。該活動的ExecuteCode屬性設置為“PostUnload”,並自動生成該方法。你將看到的設計器界面如下:

6.再次切換回工作流的源代碼文件中,向PostUnload方法中添加下面必要的代碼:

DateTime ended = DateTime.Now;
TimeSpan duration = ended.Subtract(_started);
System.Diagnostics.Trace.WriteLine(
 String.Format("*** Workflow {0} completed: {1}, duration: {2}",
        WorkflowInstanceId.ToString(),
        ended.ToString("MM/dd/yyyy hh:mm:ss.fff"),
        duration.ToString()));

7.最後一步是添加一個項目級的引用把該工作流項目引用到我們的主應用程序中,具體步驟可參考前面的章節。

備注:現在你或許想運行該程序,但請等等!假如你運行該應用程序,然後點擊“Start Workflow”按鈕而沒有點擊“Unload Workflow”按鈕的話,該應用程序不會出現運行錯誤。因為一旦工作流被卸載,但我們還沒有添加代碼來重新加載這個已被持久化(卸載)的工作流實例。因此在下一節添加代碼前,你不應單擊“Unload Workflow”按鈕。

這樣做的目的是在工作流開始執行時,在第一個Code活動中讓它休眠(sleep)10秒鐘。在此期間,你可點擊“Unload Workflow”按鈕來卸載該工作流。在這10秒屆滿後,該工作流將被卸載並持久化到數據庫中。這些事一旦發生,你就可喝杯咖啡、吃吃棒棒糖或者其它任何事休息休息:你的工作流已被保存到數據庫中,正等待再次加載。讓我們看看這又是怎麼工作的。

加載實例

WorkflowInstance公開了二個卸載方法:“Unload”和“TryUnload”,但僅僅只有一個“Load”方法,該方法不用關心工作流實例是怎樣存儲到數據庫中的。一旦它(工作流實例)被存儲,你就可使用WorkflowInstance.Load來把它再次重置到執行狀態。現在我們將向WorkflowPersister應用程序中添加合適的代碼來做這些事情。

加載被持久化的工作流

1.在Visual Studio中打開WorkflowPersister應用程序,打開該源代碼文件,找到主應用程序窗體,定位到“button3_Click”事件處理方法。

2.在“button3_Click”事件處理中添加下面的代碼:

button3.Enabled = false;
try
{
 _instance.Load();
} // try
catch (Exception ex)
{
 MessageBox.Show(String.Format("Exception while loading workflow" +
                " instance: '{0}'", ex.Message));
} // catch
button1.Enabled = true;

現在我們來看看所有這些能否真正工作。我們將運行兩次來測試工作流:一次我們直接運行到結束,一次我們將強制其卸載。然後我們比較執行時間並看看SQL Server數據庫內記錄了些什麼。

測試WorkflowPersisiter應用程序

1.按下F5鍵調試WorkflowPersisiter應用程序,如有任何編譯錯誤,請進行修正。注意該測試我們會寫一些輸出的跟蹤信息到“輸出”窗口中,因此假如沒有“輸出”窗口的話,在Visual Studio的“視圖”菜單下選擇“輸出”使其呈現。

2.點擊“Start Workflow”按鈕創建並啟動一個工作流實例,然後該“Start Workflow”按鈕會被禁用,而“Unload Workflow”按鈕將能使用。因為我們讓工作流線程休眠10秒鐘,經過10秒後,“Unload Workflow”按鈕又會自動禁用,“Start Workflow”按鈕則重新可用。在本測試中,工作流會一直運行到結束,工作流總共執行的持續時間會是10秒。

3.再一次點擊“Start Workflow”按鈕。但是,這次在這10秒的休眠期間將點擊“Unload Workflow”按鈕。該按鈕在該10秒期間內將會被禁用,過後,“Load Workflow”按鈕將能使用。在此時,你的工作流被實例化並保持被卸載狀態,直到你重新加載它。

4.但在你重新加載該工作流實例前,請打開WorkflowStore數據庫中的InstanceState表,會在該表中看到一行記錄,這行就是你的被持久化的工作流實例!

5.回到WorkflowPersister程序的執行當中,點擊“Load Workflow”按鈕。“Load Workflow”按鈕會被禁用,而“Start Workflow”按鈕將能使用。

6.結束WorkflowPersister應用程序。

7.Visual Studio輸出窗口中會包含和我們執行的兩個工作流有關的信息。

8.在輸出窗口中滾動到底部,查找我們注入的文本(我們使用三個星號“***”來裝飾這些文本)。如下所示:

假如你回頭看看InstanceState表的截圖,並把你看到的工作流實例ID和你在Visual Studio輸出窗口中看到的工作流實例ID做比較,你會看到在我們的例子中有兩個相同的實例ID:bfb4e741-463c-4e85-a9e0-c493508ec4f1。該實例花費的時間為:00:01:41.4859296,第一個工作流實例(ID為:Workflow dab11c11-9534-4097-b5bc-fd4e96cfa66c)花費的時間正如我們期望的,幾乎就為10秒鐘。兩個實例ID和執行時間有區別,但實質上是一樣的。你卸載並持久化到數據庫中的工作流實例的運行時間將超過10秒,在InstanceState表中顯示的實例ID會和Visual Studio輸出窗口中顯示的相匹配。

在空閒時加載和卸載實例

我在本章的前面部分曾經提到,在我們的工作流過程中我們將使用System.Threading.Thread.Sleep來替換Delay活動,以進行工作流的測試。在那時我說過,我選擇這樣做的原因。就持久化而言,Delay活動有一些特殊的處理能力。我們現在就來簡要地看看Delay會為我們做些什麼。

假如你在你的工作流中引入了一個Delay活動,那目的明顯是要暫停處理一段時間,不管這是一個確切的時間段還是一個不確定的暫停,該暫停會在將來的某個特定時間點,如五天後被取消。

當執行一個Delay活動時,假如工作流運行時已經附加了SqlWorkflowPersistenceService服務的話,該工作流運行時將會自動地為你持久化該工作流,而且當延時周期過期後又自動恢復它。注意,不管運行工作流運行時的系統是否被關閉、重啟等等上述事件都會發生。為了使之能自動持久化,你要在創建你的工作流運行時為SqlWorkflowPersistenceService服務添加一個特定的構造函數參數。(之前的例子省略了這些,因此工作流不會自動進行持久化。)

我提到的構造函數參數能使SqlWorkflowPersistenceService的internal方法“UnloadOnIdle”在工作流實例空閒時被調用。該方法通常不會被調用。你必須通過使用SqlWorkflowPersistenceService構造函數中的一個重載函數來明確指定這一點。在下面的例子中,將使用一個集合參數,因為你既想傳入連接字符串也想傳入空閒卸載標志。甚至還有其它更加靈活的構造函數(在本例中我們只描述這一個)。現在我們就來看看這個工作流會自動持久化的例子。

創建一個新的在空閒時持久化的工作流

1.對於這個例子,為了讓你快速領悟到在空閒時持久化是怎樣工作的,我們將使用一個簡單的基於控制台的應用程序,打開Visual Studio,創建一個新的Windows項目,該控制台應用程序的名稱命名為“WorkflowIdler”。下面的步驟,如“添加對工作流模塊的引用”、“宿主工作流運行時”,“創建WorkflowRuntime工廠對象”、“啟動工作流運行時”、“停止工作流運行時”、“使用工作流運行時工廠對象”及“處理工作流運行時事件”等過程來自第二章。

2.就像在前面例子(“創建一個新的宿主應用程序”)中的第16步和第17步一樣,修改WorkflowFactory類。但是,還有一些額外的修改工作是必要的,添加下面的語句:

using System.Collections.Specialized;

3.和前面例子(“創建一個新的宿主應用程序”)中的第18步一樣,在運行時對象被創建後添加持久化服務:

NameValueCollection parms = new NameValueCollection();
parms.Add("UnloadOnIdle", "true");
parms.Add("ConnectionString", ConfigurationManager.ConnectionStrings["StorageDatabase"].ConnectionString);
_workflowRuntime.AddService(new SqlWorkflowPersistenceService(parms));

4.和前面的例子一樣添加一個應用程序配置文件(連接字符串仍然相同)。具體過程可參考第五章中的“為你的工作流添加跟蹤服務”,那裡添加了該app.config文件。

5.創建一個單獨的順序工作流庫項目來承載我們的新工作流,工作流庫的名稱為IdledWorkflow。

6.重復前一個名稱為“創建一個新的可卸載的工作流”例子中的步驟2到步驟4。這些步驟放置了二個Code活動到你的工作流中。

7.在源代碼文件中添加下面的代碼到“PreUnload”方法中(先前一步你已添加了“PostUnload”方法的代碼)。

_started = DateTime.Now;
      System.Diagnostics.Trace.WriteLine(
        String.Format("*** Workflow {0} started: {1}",
               WorkflowInstanceId.ToString(),
               _started.ToString("MM/dd/yyyy hh:mm:ss.fff")));

8.返回到工作流視圖設計器上,拖拽一個Delay活動到兩個code活動之間。

9.指定Delay活動的“TimeoutDuration”屬性為30秒。這將有充足的時間來查看WorkflowStore數據庫中的InstanceState表。

10.工作流現在就設計完成了,向WorkflowIdler應用程序中添加對該工作流的項目引用。

11.在WorkflowIdler項目中打開Program.cs文件,找到下面的這行代碼:

Console.WriteLine("Waiting for workflow completion.");

12.當然,因為沒有工作流被啟動,該應用程序也就不會等待工作流完成。因此,創建一個工作流實例,在你找到的那行代碼下添加下面的代碼:

// Create the workflow instance.
WorkflowInstance instance =
 workflowRuntime.CreateWorkflow(typeof(IdledWorkflow.Workflow1));
// Start the workflow instance.
instance.Start();

13.按F6鍵編譯該解決方案,糾正任何彈出的編譯錯誤。現在,當你執行該WorkflowIdler應用程序時,Delay活動將強制工作流實例持久化到存儲數據庫中。然而,你會等上超過30秒(多達2分鐘)的時間,實例才被重新加載。那是因為工作流運行時在空閒狀態下由於延時,要周期性地對持久化工作流進行檢查,但它不能保證那些工作流將僅僅等上所期望的延時時間。WF會周期性地輪詢數據庫,尋找那些正等待計時器事件的空閒並已被持久化的工作流(Delay活動使用了一個計時器)。默認的輪詢時間是2分鐘。

備注:可以更改默認的數據庫輪詢時間,方法是使用SqlWorkflowPersistenceService服務時為其提供一個TimeSpan,可使用四個參數的構造函數(分別是連接字符串、工作流處於空閒狀態時是否卸載的標志、工作流保持鎖定的時間長度以及持久化服務輪詢數據庫以查找計時器已過期的工作流的頻率)。

本文配套源碼

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