本文章是由機器翻譯。

ASP.NET 工作流

支援長時間運行操作的 Web 應用程式

Michael Kennedy

代碼下載位置:MSDN 代碼庫
線上流覽代碼

本文將介紹以下內容:

  • 獨立于進程的工作流
  • 同步和非同步活動
  • 工作流、活動和持久性
  • 與 ASP.NET 集成
本文使用了以下技術:
Windows Workflow Foundation、ASP.NET

目錄

管理工作流
同步和非同步活動
“空閒”究竟指什麼?
同步任務非同步化
工作流和活動
持久性
使之變為現實
與 ASP.NET 集成
考慮事項
綜述

人們經常要求軟體發展人員構建可支援長時間運行操作的 Web 應用程式。 線上商店的結帳過程就是一個例子,它只需數分鐘即可完成。 儘管依照某些標準這就是一個長時間運行操作,但我將在本文中探討一個尺度完全不同的長時間運行操作:需持續數天、數周,甚至數月才可完成的操作。 此類操作的一個示例是職位的申請過程,它涉及多人之間的交互以及眾多實際文檔的交換。

首先,我們從 ASP.NET 角度來考慮一個較為良性的問題:您需要為線上商店的結帳操作構建一個解決方案。 由於其持續時間的特殊性,我們需對此解決方案特別考慮一些事項。 例如,您可能會選擇在某個 ASP.NET 會話中存儲購物車資料。 您甚至可以選擇將該會話狀態移動到進程外狀態伺服器或資料庫,以更新網站和負載平衡。 即便如此,您會發現輕鬆解決此問題所需的全部工具均由 ASP.NET 自身提供。

但如果操作的持續時間變得比典型的 ASP.NET 會話持續時間(20 分鐘)更長,或需要多名參與者(就象剛才的聘用示例)時,ASP.NET 不能提供充分的支援。 您也許還記得 ASP.NET 工作進程會在空閒時自動關閉並定期自身回收。 這會導致長時間運行操作出現嚴重錯誤,因為這些進程中保存的狀態將會丟失。

設想一下,您將需要在單個進程內部託管這些執行時間超長的操作。 顯然,出於上述原因,ASP.NET 工作進程並不適用于它們。 因此需要創建一個視窗服務,它的職責就是執行這些操作。 如果從不重新開機此服務,將會比直接使用 ASP.NET 更有可能得到解決方案,因為從理論上講,擁有無法自動重新開機的服務進程可確保不會丟失長時間運行操作的狀態。

但這樣真的可以解決該問題嗎?很可能不會。 如果伺服器需要負載平衡該怎麼辦?如果思路僅局限于單個進程將會使情況變得非常困難。 更糟糕的是,如果需要重新開機伺服器或進程崩潰該怎麼辦? 如果這樣將會導致丟失已運行的所有操作。

事實上,當操作需要數天或數周才可完成時,您就需要一個獨立于執行該操作進程的生命週期的解決方案。 通常這是一個不爭的事實,對於 ASP.NET Web 應用程式更是尤為重要。

管理工作流

Windows Workflow Foundation (WF) 可能並不是構建 Web 應用程式時想到的技術。 但 WF 提供的多個主要功能讓工作流解決方案有了不同尋常的意義。 有了 WF,您就能通過從進程空間中徹底卸載空閒工作流,並在工作流繁忙時自動將它們重新載入到活動進程中,讓長時間運行的操作具備進程獨立性(請參見圖 1)。 利用 WF 可以克服 ASP.NET 工作進程不確定生命週期的影響,並為 Web 應用程式內的長時間運行操作做好準備。

fig01.gif

圖 1 跨進程實例的工作流保留操作

WF 組合了兩個主要特性來實現此功能。 首先,非同步活動在等待外部事件時向工作流運行時發出工作流空閒的信號。 接下來,持久性服務從該進程卸載空閒的工作流,然後將其保存到某個持久存儲位置(如資料庫)並在做好再次運行準備時重新載入工作流。

這樣的進程獨立性還有其他的優點。 它提供了一種簡單的負載平衡方式及持久性——在遇到進程或伺服器故障時能實現容錯。

同步和非同步活動

活動是 WF 的原子元素。 所有的工作流都是通過與複合設計模式相似的方式使用活動構建的。 實際上工作流本身就是經過簡單特殊處理的活動。 這些活動分為同步或非同步兩類。 同步活動自始至終都在執行其所有指令。

例如,線上商店得到訂單後計算稅金就是同步活動。 我們來看一下如何執行此類活動。 與大多數 WF 活動一樣,大部分工作都是在替換 Execute 方法時發生的。 執行該方法的步驟可能類似如下所示:

  1. 從上一個活動獲取訂單資料。 這通常是通過資料綁定完成的,稍後您可以看到它的示例。
  2. 從資料庫中查找與該訂單關聯的客戶。
  3. 根據該客戶的位置從資料庫中查找稅率。
  4. 使用稅率和與訂單關聯的訂單專案進行一些簡單的數學運算。
  5. 將稅金總額存儲在與後續活動綁定的屬性中以完成結帳過程。
  6. 通過從 Execute 方法返回“完成”狀態標記向工作流運行時通知該活動已完成。

注意:您沒有等候狀態。 一直處於工作狀態中。 Execute 方法只需要運行這些步驟並快速地完成即可。 這就是同步活動的本質:所有工作都在 Execute 方法中完成。

非同步活動則不同。 與其對應的同步活動不同,非同步活動執行一段時間後,會等待外部促進因素。 在等待時,活動變為空閒狀態。 事件發生後,活動繼續操作並完成執行。

例如,錄用過程中,經理審核職位申請就屬於非同步活動。 考慮一下如果經理正在度假,一周內無法審核申請將會發生什麼情況。 在等待此回應時,中途阻止 Execute 方法是完全不可取的。 等待某個人員來運行軟體可能需要等候很長的時間。 您需要在設計中考慮到這種情況。

“空閒”究竟指什麼?

這個詞的英語語義和體系結構語義有分歧。 脫離 WF,按常規思考一下“空閒”的含義。

請考慮下列使用 Web 服務更改密碼的類:

public class PasswordOperation : Operation {
  Status ChangePassword(Guid userId, string pw) {
    // Create a web service proxy:
    UserService svc = new UserService();

    // This can take up to 20 sec for 
    // the web server to respond:
    bool result = svc.ChangePassword( userId, pw );

    Logger.AccountAction( "User {0} changed pw ({1}).",
      userId, result);
    return Status.Completed;
  }
}

ChangePassword 方法是否空閒?如果是,那麼位置在哪裡?

此方法的執行緒在等待來自 UserService 的 HTTP 回應時受阻。 如此,從概念上講,該執行緒處於空閒狀態並在等候伺服器回應。 但實際上該執行緒在等候服務時可進行其他工作嗎?不可以,因為它目前仍在使用中。 因此,從 WF 的角度來看,此“工作流”永遠不會空閒。

為什麼永遠不會空閒呢?假設您有一些可有效運行諸如 ChangePassword 操作的大型調度程式類。 此處的“有效”是指並行運行多個操作,使用完全並行所需的最小數量的執行緒等。 事實證明,實現此有效性的關鍵在於瞭解何時執行操作以及何時操作處於空閒狀態。 因為在操作變為空閒時,調度程式可使用運行該操作的執行緒來執行其他工作,直至該操作準備再次運行。

令人遺憾的是,ChangePassword 方法對於調度程式是完全不透明的。 因為儘管會有某個時段其處於有效空閒狀態,但調度程式從外部觀察時該方法仍是阻止工作的一個單元。 在空閒時段,調度程式無法將該工作單元分割開並重新使用該執行緒。

同步任務非同步化

您可以通過將該操作分割為兩部分來將這個所需的調度透明性添加到操作中:一部分在操作空閒之前執行操作,另一部分在空閒狀態後執行代碼。

對於之前所示的假定示例,您可以使用 Web 服務代理自身提供的非同步功能。 請務必記得這只是一種簡化的情形,事實上 WF 的工作方式稍有不同,稍後即可明白這一點。

圖 2 中,我創建了 password-changing 方法的改進版本,稱為ChangePasswordImproved。 我像以前一樣創建了 Web 服務代理。 然後該方法註冊一個回檔方法以在伺服器回應時獲得通知。 接下來,我將非同步執行該服務調用並通過返回 Status.Executing 告訴調度程式該操作處於空閒狀態,但尚未完成。 這個步驟很重要,因為它能讓調度程式在代碼空閒時完成其他工作。 最後,事件完成時,我會調用調度程式通知操作已完成,可以繼續執行後續步驟。

圖 2 簡單的 Password-Changing 服務調用

public class PasswordOperation : Operation {
  Status ChangePasswordImproved(Guid userId, string pw) {
    // Create a web service proxy:
    UserService svc = new UserService();

    svc.ChangePasswordComplete += svc_ChangeComplete;
    svc.ChangePasswordAsync( userId, pw );
    return Status.Executing;
  }

  void svc_ChangeComplete(object sender, PasswordArgs e) {
    Logger.AccountAction( "User {0} changed pw ({1}).",
      e.UserID, e.Result );

    Scheduler.SignalCompleted( this );
  }
}

工作流和活動

現在,我將應用操作空閒的概念在 WF 中構建活動。 它與您之前看到的內容非常相似,但我現在必須在 WF 模型內工作。

WF 帶有很多內置活動。 但如果您是第一次著手利用 WF 構建實際系統,您很快就會想為自己構建可重用的自訂活動。 這個很簡單。 您只需要定義一個從常用 Activity 類派生的類即可。 下麵是一個基本示例:

class MyActivity : Activity {
  override ActivityExecutionStatus 
    Execute(ActivityExecutionContext ctx) {

    // Do work here.
    return ActivityExecutionStatus.Closed;
  }
}

要使活動執行一些有用的操作,必須覆蓋 Execute 方法。 如果構建的是短期存在的同步活動,可直接在此方法內實現活動的操作,然後返回狀態“Closed”(關閉)。

在實際的活動中很可能需要考慮幾個大問題。 您的活動如何與工作流內的其他活動以及託管工作流的大型應用程式通信?它如何訪問服務(如資料庫系統、UI 交互等)?在構建同步活動時,這些問題相對而言比較簡單。

但在構建非同步活動時問題就要複雜多了。 幸運的是,所使用的模式在大多數非同步活動中都是重複的。 事實上,您可以輕鬆地在基類中捕獲此模式,稍後我將對此加以說明。

構建大多數非同步活動時均需要下列基本步驟:

  1. 創建一個派生自 Activity 的類。
  2. 覆蓋 Execute 方法。
  3. 創建一個工作流佇列,用於接收所等待的非同步事件已完成的通知。
  4. 訂閱該佇列的 QueueItemAvailable 事件。
  5. 啟動長時間運行操作(例如發送電子郵件請經理審核工作崗位的申請)。
  6. 等待外部事件發生。 這樣可以有效地發出該活動已變為空閒的信號。 可通過返回 ExecutionActivityStatus.Executing 向工作流運行時指出此情況。
  7. 事件發生時,處理 QueueItemAvailable 事件的方法會從佇列中刪除該項,然後將其轉換為期望的資料類型並處理結果。
  8. 通常這樣會終止活動的操作。 然後工作流運行時通過返回 ActivityExecutionContext.CloseActivity 發出信號。

持久性

在本文的開頭,我就提到過通過工作流實現進程獨立需要兩個基本條件:非同步活動和持久性服務。 您剛才看到的是非同步活動的說明。 現在來深入探討持久性的深層技術:工作流服務。

工作流服務是 WF 的主要擴展點。 WF 運行時是一個在應用程式中產生實體以託管所有運行工作流的類。 此類有兩個相悖的設計目標,它們可通過工作流服務的概念同時實現。 此工作流運行時的第一個目標是成為可在多處使用的輕型物件。 此工作流運行時的第二個目標是在工作流運行時向其提供多個強大的功能。 例如,自動保持空閒工作流、跟蹤工作流進度及支援其他自訂功能等。

預設情況下,工作流運行時保持輕型狀態,因為這些功能中只有一對是內置功能。 更多的重型服務(如持久性和跟蹤)是通過服務模型選擇安裝的。 事實上,服務的定義可以是您希望向工作流提供的任何全域功能。 您只需調用 WorkflowRuntime 類的 AddService 方法即可在運行時中安裝這些服務:

void AddService(object service)

由於 AddService 採用了 System.Object 引用,您可以添加工作流所需的任何內容。

我將使用兩項服務。 首先,使用 WorkflowQueuingService 訪問構建非同步活動的基礎工作流佇列。 預設情況下,此服務已安裝且無法自訂。 另一個服務為 SqlWorkflowPersistenceService。 當然,此服務會提供持久性功能且預設未安裝。 幸運的是,WF 中已包含這項服務。 您只需要將其添加到運行時即可。

從類似 SqlWorkflowPersistenceService 這樣的名稱推斷,您可以確信必定有某處需要資料庫。 您可以為此創建空資料庫,或者向現有資料庫添加新表。 我個人傾向使用專門的資料庫,而不是將工作流持久性資料與其他資料混合在一起。 因此我在 SQL Server 中創建了一個名為 WF_Persist 的空資料庫。 我通過運行兩個腳本來創建所需的資料庫架構和存儲過程。 它們是隨同 Microsoft .NET Framework 一起安裝的,預設資料夾是:

C:\Windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN\

您將希望首先運行 SqlPersistenceService_Schema.sql 腳本,然後運行 SqlPersistenceService_Logic.sql 腳本。 現在,我可以通過將連接字串傳遞給持久性服務來將此資料庫用於持久性:

SqlWorkflowPersistenceService sqlSvc = 
    new SqlWorkflowPersistenceService(
  @"server=.;database=WF_Persist;trusted_connection=true",
  true, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));

wfRuntime.AddService(sqlSvc);

如果想先卸載空閒工作流並將其存儲在資料庫中,然後在需要時還原它們,這個簡單的 AddService 方法調用就能完成全部工作。 WF 運行時還負責其他所有事情。

使之變為現實

既然已有了足夠的技術基礎,您就可以將它們組合起來構建一個支援長時間運行操作的 ASP.NET Web 網站。 在此示例中您將看到三個主要元素:非同步活動的結構、將工作流運行時集成到 Web 應用程式中以及在 Web 頁面中與工作流通信。

我將設計一個名為 Trey Research 的假想 .NET 諮詢公司。 他們希望為顧問自動化招聘和錄用過程。 因此我將構建一個 ASP.NET Web 網站以支援此錄用過程。 我儘量對操作進行簡化,但還是要有幾個步驟:

  1. 求職者訪問 Trey Research Web 網站並表示對某個工作崗位感興趣。
  2. 向經理髮送電子郵件通知有新的申請人。
  3. 該經理將審核申請並批准求職者得到某個特定的職位。
  4. 向該求職者發送電子郵件告知所提議職位的相關資訊。
  5. 求職者會訪問該 Web 網站接受或拒絕該職位。

儘管此過程很簡單,但仍有幾個步驟需要申請等候某個人進行回復並填寫更多資訊。 這些空閒點將會花費很長的時間。 因而這種情況正是長時間運行進程大展身手的好時機。

此 Web 應用套裝程式括在本文的原始程式碼中。 但要看到完整的效果還需要創建持久性資料庫並配置示例加以使用。 我創建了一個開關,用於控制持久性服務的打開或關閉,預設將其設置為關閉。 要打開,可將 web.config 的 AppSettings 中的 usePersistDB 設置為 true。 要查看其運行效果,可訪問我的網站上的 非同步工作檢視器運行。

fig03.gif

圖 3 錄用工作流

我將從設計完全獨立于 ASP.NET 的工作流開始著手。 為構建該工作流,我將創建四個自訂活動。 第一個活動是發送電子郵件,這是一個簡單的同步活動。 其他三個活動分別表示之前所示的步驟 1、3 和 5,這是三個非同步活動。 這些活動是長時間運行操作成功的關鍵。 我將它們分別稱作 GatherEmployeeInfoActivity、AssignJobActivity 和 ConfirmJobActivity。 然後將這些活動合併到工作流框架中,如圖 3 所示。

發送電子郵件活動比較簡單,因此無需在本文中深入討論該活動的細節。 這是一個同步活動,與之前所示的 MyActivity 類很相似。 有關詳細資訊請查看所代碼下載。

接下來要創建三個非同步活動。 如果能將構建非同步活動的八步過程壓縮為一個通用基類就可以省去一大部分工作了。 為此,我將定義一個名為 AsyncActivity 的類(請參見圖 4)。 請注意,此清單並不包括幾種內部輔助方法或實際代碼中所提供的錯誤處理。 為了簡潔起見,這些細節均已省去。

圖 4 AsyncActivity

public abstract class AsyncActivity : Activity {
  private string queueName;

  protected AsyncActivity(string queueName) {
    this.queueName = queueName;
  }

  protected WorkflowQueue GetQueue(
      ActivityExecutionContext ctx) {
    var svc = ctx.GetService<WorkflowQueuingService>();
    if (!svc.Exists(queueName))
      return svc.CreateWorkflowQueue(queueName, false);

    return svc.GetWorkflowQueue(queueName);
  }

  protected void SubscribeToItemAvailable(
      ActivityExecutionContext ctx) {
    GetQueue(ctx).QueueItemAvailable += queueItemAvailable;
  }

  private void queueItemAvailable(
      object sender, QueueEventArgs e) {
    ActivityExecutionContext ctx = 
      (ActivityExecutionContext)sender;
    try { OnQueueItemAvailable(ctx); } 
    finally { ctx.CloseActivity(); }
  }

  protected abstract void OnQueueItemAvailable(
    ActivityExecutionContext ctx);
}

在此基類中,您會看到我總結的構建非同步活動的幾個單調重複的部分。 讓我們從此類開始逐個研究一翻。 由構造函數開始,我傳入一個字串作為佇列名稱。 工作流佇列是託管應用程式(Web 頁面)的輸入點,用於在保持鬆散耦合的同時將資料傳入到活動中。 這些佇列是按名稱和工作流實例引用的,因此每個非同步活動都需要有其自己獨有的佇列名稱。

然後,我定義了 GetQueue 方法。 如您所見,訪問和創建工作流佇列很容易,但有些單調。 我創建的這一方法可作為輔助方法在此類和派生類中使用。

之後定義了一個名為 SubscribeToItemAvailable 的方法。 此方法封裝了某個專案到達工作流佇列時所觸發事件的訂閱細節。 這通常表示長時間等待結束,工作流已空閒。 因此,所用示例如下所示:

  1. 開始長時間運行操作並調用 SubscribeToItemAvailable。
  2. 通知工作流運行時該活動處於空閒狀態。
  3. 工作流實例通過持久性服務序列化為資料庫。
  4. 操作結束時,將某個專案發送到工作流佇列中。
  5. 這將觸發工作流實例從資料庫中還原。
  6. 抽象範本方法 OnQueueItemAvailable 由基類 AsyncActivity 執行。
  7. 該活動完成其操作。

要查看此 AsyncActivity 類的運轉狀況,需實現 AssignJobActivity 類。 其他兩個非同步活動很相似,均已包括在代碼下載中。

圖 5 中,您可以瞭解到 AssignJobActivity 是如何使用 AsyncActivity 基類提供的範本的。 我覆蓋了 Execute 方法,以完成長時間運行活動的前期準備(儘管此示例中實際並沒有前期準備)。 然後訂閱事件以瞭解何時有更多資料可用。

圖 5 AssignJobActivity

public partial class AssignJobActivity : AsyncActivity {
  public const string QUEUE NAME = "AssignJobQueue";

  public AssignJobActivity()
    : base(QUEUE_NAME) 
  {
    InitializeComponent();
  }

  protected override ActivityExecutionStatus Execute(
      ActivityExecutionContext ctx) {
    // Runs before idle period:
    SubscribeToItemAvailable(ctx);
    return ActivityExecutionStatus.Executing;
  }

  protected override void OnQueueItemAvailable(
      ActivityExecutionContext ctx) {
    // Runs after idle period:
    Job job = (Job)GetQueue(ctx).Dequeue();

    // Assign job to employee, save in DB.
    Employee employee = Database.FindEmployee(this.WorkflowInstanceId);
    employee.Job = job.JobTitle;
    employee.Salary = job.Salary;
  }
}

此處有一個隱式約定,在從經理那裡收集到新的 Job 物件後,託管應用程式(Web 頁面)會將其發送到該活動的佇列中。 此操作將告訴活動可以繼續。 這會更新資料庫中的員工。 工作流中的下一個活動會向潛在員工發送電子郵件,通知向他推薦此職位。

與 ASP.NET 集成

這就是其在工作流內部的工作方式。 但是如何啟動工作流呢?實際中的 Web 頁面如何收集經理提供的工作邀請呢?如何將 Job 傳送給活動呢?

我們首先要明確的是:如何啟動工作流。 在 Web 網站的登錄頁面上有一個“Apply Now”(立即申請)連結。 求職者按一下此連結時,將通過使用者介面並行啟動工作流和導航:

protected void LinkButtonJoin_Click(
    object sender, EventArgs e) {
  WorkflowInstance wfInst = 
    Global.WorkflowRuntime.CreateWorkflow(typeof(MainWorkflow));

  wfInst.Start();
  Response.Redirect(
    "GatherEmployeeData.aspx?id=" + wfInst.InstanceId);
}

我只需在工作流運行時調用 CreateWorkflow 並啟動工作流實例即可。 之後,我將實例 ID 作為查詢參數傳遞給所有後續 Web 頁面,以此跟蹤該工作流實例。

如何將 Web 頁面中的資料發送回工作流呢?我們來看分配工作的頁面(如 圖 6 所示),經理為某個求職者選擇了一份工作。

圖 6 分配工作

public class AssignJobPage : System.Web.UI.Page {
  /* Some details omitted */
  void ButtonSubmit_Click(object sender, EventArgs e) {
    Guid id = QueryStringData.GetWorkflowId();
    WorkflowInstance wfInst = Global.WorkflowRuntime.GetWorkflow(id);

    Job job = new Job();
    job.JobTitle = DropDownListJob.SelectedValue;
    job.Salary = Convert.ToDouble(TextBoxSalary.Text);

    wfInst.EnqueueItem(AssignJobActivity.QUEUE_NAME, job, null, null);

    buttonSubmit.Enabled = false;
    LabelMessage.Text = "Email sent to new recruit.";
  }
}

分配工作的 Web 頁面大體上是一個簡單的輸入表格。 它包括一個可用工作的下拉清單和一個輸入提議薪酬的文字方塊。 還顯示當前的求職者,儘管清單中已忽略該代碼。 當經理為求職者分配職位和薪酬時,可按一下提交按鈕然後運行圖 6 中的代碼。

此頁面將工作流實例 ID 用作查詢字串參數,查找關聯的工作流實例。 然後使用表格中的值創建並初始化 Job 物件。 最後,通過將工作排入該活動的佇列中將此資訊發送回活動。 這是重新載入空閒工作流並允許其繼續執行的關鍵步驟。 AssignJobActivity 將此工作與之前收集的員工建立關聯,然後將它們保存到資料庫中。

後兩個代碼列出項強調了基本工作流佇列對非同步活動和工作流與外部主機成功通信的作用。 還應注意這裡所使用的工作流對於頁面流沒有任何影響。 儘管也可以使用 WF 控制頁面流,但這並不是本文的重點。

在 圖 6 中,您會看到通過全域應用程式類訪問工作流運行時,如下所示:

WorkflowInstance wfInst = 
  Global.WorkflowRuntime.GetWorkflow(id);

這樣就完成了將 Windows Workflow 集成到 Web 應用程式中的過程:所有工作流都在工作流運行時內執行。 儘管 AppDomain 中的工作流運行時的數量不受限制,但通常最好使用單個工作流運行時。 有鑑於此,再加上 WF 運行時物件具有線程安全性,我將其變為全域應用程式類的某個公共靜態屬性。 此外,我在應用程式啟動事件中啟動該工作流運行時,在應用程式終止事件中停止該工作流運行時。 圖 7 是簡化的全域應用程式類。

圖 7 啟動工作流運行時

public class Global : HttpApplication {
  public static WorkflowRuntime WorkflowRuntime { get; set; }

  protected void Application_Start(object sender, EventArgs e) {
    WorkflowRuntime = new WorkflowRuntime();
    InstallPersistenceService();
    WorkflowRuntime.StartRuntime();
    // ...
  }

  protected void Application_End(object sender, EventArgs e) {
    WorkflowRuntime.StopRuntime();
    WorkflowRuntime.Dispose();
  }

  void InstallPersistenceService() {
    // Code from listing 4.
  }
}

在應用程式啟動事件中,我創建了運行時,安裝了持久性服務並啟動了該運行時。 在應用程式終止事件中停止了該運行時。 這一步非常重要。 如果這些工作流卸載前仍存在運行時工作流,將會發生阻斷。 停止該運行時後,我調用了 Dispose。 先調用 StopRuntime 再調用 Dispose 可能看起來很多餘,但實際情況並非如此。 您需要依照該次序來調用這兩個方法。

考慮事項

現在我請大家思考一些我未直接談及的內容,這些內容以問答形式提出。 我為什麼不使用 ManualWorkflowSchedulerService?通常人們在談到 WF 與 ASP.NET 集成時,會強調應將使用執行緒池的工作流的預設調度程式替換成一項名為 ManualWorkflowSchedulerService 的服務。 這是由於該調度程式可能並不需要或者確實不適用於長時間運行。 當您期望在某個給定的請求內完成單個工作流時,手動調度程式是個不錯的選擇。 但工作流跨進程生命週期執行就沒多大意思了,跨請求更是如此。

是否有方法可以跟蹤某個給定工作流實例的當前進程呢?有的,WF 中已構建了一項完整的跟蹤服務,其使用方式與 SQL 持久性服務相似。 請參閱 2007 年 3 月刊“基礎內容”專欄中 Matt Milner 撰寫的“ Windows Workflow Foundation 中的跟蹤服務”。

綜述

我可以用幾個步驟歸納本文中所討論的技術。 首先我講述了 ASP.NET 工作進程和進程模型通常不適用於長時間運行操作的原因。 為了擺脫這種局限,我組合了 WF 中的兩個功能實現進程獨立性:非同步活動和工作流持久性。

由於構建非同步活動稍微有些難度,我將很多細節封裝到了本文所介紹的 AsyncActivity 基類中。 然後將長時間運行動作表述為用非同步活動構建的順序工作流,我可以將其封裝到 Web 應用程式中,從而輕鬆實現進程獨立。

最後,我介紹了將工作流集成到 ASP.NET 中的兩項基本內容:通過工作流佇列與活動通信以及在全域應用程式類中託管運行時。

您已看到,將 WF 與 ASP.NET 集成在一起可支援長時間運行操作,它是一個功能更為強大的工具,可在 .NET Framework 之上構建解決方案。

Michael Kennedy 是 DevelopMentor 的一名講師,專門負責核心 .NET 技術以及敏捷和 TDD 開發方法。 您可以通過 Michael 的網站和博客與其聯繫,位址為 michaelckennedy. net