程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Windows Workflow Foundation

Windows Workflow Foundation

編輯:關於.NET

在 2006 年 1 月號中,Don Box 和 Dharma Shukla 介紹了 Windows® Workflow Foundation,並討論了框架的整體體系結構及其構成組件(請參閱 WinFX Workflow:Simplify Development With The Declarative Model Of Windows Workflow Foundation [英文])。這篇文章促使我想進一步討論這個主題,並介紹如何使用 Windows Workflow Foundation 來處理自動進程與人工活動貫穿相交的這種常見業務方案。它為開發和執行基於復雜過程的多種應用程序提供了框架。典型示例包括文檔管理應用程序、企業對企業應用程序和企業對消費者應用程序。用戶可以使用 Visual Studio® 2005 幫助設計基礎工作流以及有關的頂級應用程序和程序集。

常見業務方案

對於訂單處理、采購申請、差旅費用之類的任務,各組織通常會設有許多內部進程。工作流使這些獨立進程以透明、動態、有效的方式按順序進行。

讓我們看一個典型的技術支持工作流進程。在技術支持人員接到客戶電話,並開立記錄客戶姓名、來電時間和問題簡要說明的票證時,進程即算開始。創建票證之後,該技術支持人員會將這件事放在一邊,並等待其他來電。下班時,他將注銷計算機,然後回家。此時,在另一個部門中(可能位於其他城市),一組技術人員正專注解決這些未解決的問題。每個工作的技術人員要選取申請,然後解決該申請或將該申請升級到第二級幫助。如何編寫代碼來實現此進程?

可以使用 Windows 窗體應用程序來收集電話的相關輸入數據,並在數據庫中創建一個記錄:即包含時間、說明、狀態和唯一 ID 的票證。第二個 Windows 窗體應用程序的用戶將看到待處理申請的實時列表,然後選取一個申請。然後,接線員將盡量解決問題(回電給客戶、檢索申請的信息、發送電子郵件或執行一些遠程活動),並指明問題是已解決還是需要進一步研究。此決策可由一個命令性操作表示,例如單擊某個按鈕更新同一基礎數據庫中的票證。最後,如果還涉及其他類別的用戶,自定義前端將使這些用戶能夠指明問題已成功關閉或中止。

盡管此過程明確地表達了工作流需要用戶進行某些決策,但可以使用以標准編程語言和數據庫編寫的傳統順序代碼輕松實現。

應用 Windows Workflow Foundation

如果用戶具備由各活動組成的基於工作流的系統(如 Windows Workflow Foundation),則可利用命令性代碼和聲明性活動地圖的強大組合以及綁定它們的聲明性規則來實現應用程序。主要好處在於用戶可以為解決方案建模(甚至以直觀方式建模),將 Windows Workflow 嵌入運行時服務器來解釋圖表,並使 Windows Workflow 遵循在創建塊中定義的鏈接。進程越復雜,為其設計和實現的流程就越簡單。進程動態更改越容易,用戶需要編寫和維護的代碼數量就越少。讓我們了解一下如何實現技術支持方案的 Windows Workflow Foundation 解決方案。

技術支持解決方案

通過創建票證,創建的技術支持工作流程即開始,然後在等待連接用戶或技術人員給予響應時停止。無論票證是關閉還是升級,工作流都將獲得外部事件,並更新應用程序的內部狀態以跟蹤該事件。因此,工作流需要與外界進行交互。這類異步活動是 Windows Workflow Foundation 解決的實際工作流進程的固有問題之一。因為需要與系統外部的實體進行交互,所以宿主應用程序和工作流可以定義約定,以進行任何必要的數據交換。此處顯示的 IHelpDeskService 接口說明了在工作流及其宿主之間建立的通信接口:

[DataExchangeService]
public interface IHelpDeskService
{
event EventHandler<HelpDeskTicketEventArgs> TicketClosed;
event EventHandler<HelpDeskTicketEventArgs> TicketEscalated;
void CreateTicket(
string description, string userRef, string createdBy);
void CloseTicket(string ticketID);
void EscalateTicket(string ticketID);
}

現在,開始編寫名為 HelpDeskService 的服務類,如 圖 1 所示。HelpDeskService 類將添加到工作流運行時,表示宿主應用程序和工作流之間的接觸點。宿主應用程序將調用該類的公共方法以激發對工作流的外部事件。激發的事件會以信號形式通知將指導操作流的特定於域的事件。IHelpDeskService 接口上的方法僅表示工作流可以通過 InvokeMethod 活動在外部組件上調用的部分代碼。以此方法計算出的 HelpDeskService 的邏輯能夠在很大程度上增加工作流的靈活性,因為它基本上可以使工作流回調宿主,以真正實現基本操作。通過包含實現 IHelpDeskService 接口的類,不同宿主可以使用不同算法和存儲媒體創建、解決或升級票證,同時保持工作流邏輯不變。用戶可以在其他程序集中編譯接口和類,也可以僅將這些文件保留在定義工作流的同一程序集中。

Figure 1 Implementing Workflow with HelpDeskService

using System;
using System.Threading;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Messaging;
namespace MySamples
{
  public class HelpDeskService : IHelpDeskService
  {
    // Implement events
    public event EventHandler<HelpDeskTicketEventArgs> TicketClosed;
    public event EventHandler<HelpDeskTicketEventArgs>
      TicketEscalated;
    public void RaiseCloseTicketEvent(Guid instanceId)
    {
      // Raise the event to the workflow
      HelpDeskTicketEventArgs args =
        new HelpDeskTicketEventArgs(instanceId, "");
      if (TicketClosed != null) TicketClosed(this, args);
    }
    public void RaiseEscalateTicketEvent(Guid instanceId)
    {
      // Raise the event to the workflow
      HelpDeskTicketEventArgs args =
        new HelpDeskTicketEventArgs(instanceId, "");
      if (TicketEscalated != null) TicketEscalated(this, args);
    }
    void IHelpDeskService.CreateTicket(
      string description, string userRef, string createdBy)
    {
      // Fill up a ticket: same ID as the workflow
      string ticketID =
        WorkFlowEnvironment.CurrentInstanceId.ToString();
      // Create ticket on the DB
      HelpDeskHelpers.CreateTicket(
        ticketID, description, userRef, createdBy);
    }
    void IHelpDeskService.CloseTicket(string ticketID)
    {
      // Update ticket on the DB
      HelpDeskHelpers.UpdateTicket(ticketID, TicketStatus.Closed);
    }
    void IHelpDeskService.EscalateTicket(string ticketID)
    {
      // Update ticket on the DB
      HelpDeskHelpers.UpdateTicket(ticketID,
        TicketStatus.Escalated);
    }
  }
}

圖 1

HelpDeskWorkflow 是 Windows 窗體應用程序駐留的順序工作流。圖 2 顯示的是其圖形模型。它開始於 InvokeMethod 活動,該活動導致應用程序基於用戶提供的信息創建票證。然後,票證將放置在數據庫中,等待技術人員選取,以進行解決或升級。

圖 2 Visual Studio 2005 中的技術支持工作流

創建票證之後,工作流將進入等待狀態,可能會等待很長時間 - 甚至幾小時或幾天。這段時間內會出現什麼情況?是否應該將工作流實例加載到內存中?如果工作流空閒,可以將其從內存中卸載 - 這一進程稱為“鈍化”。本地服務(如技術支持服務)保留宿主應用程序和休眠工作流之間的主要接觸點。

人為因素

當人為因素與宿主應用程序進行交互並執行一些操作來喚醒工作流時,本地服務將向運行時發布請求來恢復鈍化的工作流。Windows Workflow Foundation 工具箱包含一個稱為 Listen 的活動,該活動只是使工作流空閒並偵聽傳入的喚醒呼叫。圖 2 中標有 WaitForSolution 的塊是 Listen 活動的實例。

如果沒有一個或多個子分支(每個分支表示一個可掛接在此點的可能發生的事件),Listen 活動基本上沒有意義。在該技術支持示例中,Listen 活動包含兩個分支 - 票證關閉和票證升級。每個分支都是一個 EventDriven 活動。Listen 活動和 EventDriven 活動都不需要特別設置,它們只是子活動的容器。要捕獲外部事件,工作流需要 EventSink 組件。

在圖 2 中,TicketClosed 塊是綁定到本地技術支持服務中的 TicketClosed 事件的 EventSink。圖 3 列出了此處設置的 EventSink 屬性。首先,請選擇提供事件的服務接口。必須設置 InterfaceType 實現,此外,僅能選擇以 [DataExchangeService] 屬性修飾的接口,如圖 1 所示。

圖 3 EventSink 屬性

設置數據交換接口後,將使用接口中找到的所有事件預填充 EventName 屬性。用戶選取選擇的事件並設置其參數。如果希望在事件得到處理後得到進一步的通知,還要設置 Invoked 屬性。

在等待人為干預的一段空閒時間之後,EventSink 將控制任務返回給工作流。事件發生後,繼續進行與工作流相應的其他任何活動。例如,在技術支持應用程序中調用本地服務的另一個方法,以更新票證數據庫中的票證狀態。

在實際情況下,工作流至少需要與一個數據庫直接或間接交互。在此示例中,使用的是 SQL Server™ 2000 表 Tickets,其中每行都對應於一個正在處理的票證。票證 ID 特意設置為與用於管理票證的工作流 ID 相匹配。

技術支持前端

工作流經編譯後,只是一個可重用的程序集,因此可以被任何類型的基於 .NET 的應用程序引用。現在,讓我們為技術支持接線員來構想一個前端應用程序。該應用程序是 Windows 窗體程序,允許用戶填充表單,並創建票證以啟動工作流,如圖 4 所示。

圖 4 技術支持前端應用程序

工作流調用本地服務的 CreateTicket 方法,並使服務在票證數據庫中添加新記錄。在接線員開立新票證之後,工作流將開始並進入空閒狀態等待人為干預,而該接線員將繼續接聽電話並回復電子郵件。每個票證都由工作流的不同實例來表示。為了方便起見,工作流 ID 也是票證的 ID。

狀態持久性

在一天結束的時候,工作流成為一個活動樹,如何管理其保留時間?Windows Workflow Foundation 運行時引擎會管理所有工作流的執行,使工作流長時間保持活動狀態,甚至在重新啟動計算機時也不會受到影響。運行時引擎由可插拔式服務提供支持,這些服務提供了完整豐富的執行環境 - 事務、持久性、跟蹤、計時器和線程。

除非有某些干預使工作流繼續進行,否則工作流不能保留在主機進程的內存中。如前所述,許多實際基於人為干預的工作流可能需要等待幾個小時(甚至更長時間)才能繼續。在前面的示例中,技術支持接線員啟動了工作流,並在前端應用程序進程中加載了工作流類。然後,該接線員就可以關閉計算機回家了。但是,票證必須保持激活狀態,並且對於其他接線員(甚至其他應用程序內的接線員)可用。因此,工作流必須支持對長效存儲媒體的序列化。

工作流可能是一個長時間運行的操作,不適用於那些在內存中連續幾天保持激活狀態的對象。首先,主機進程通常不能為連續幾天活動所積累的所有對象提供緩存;其次,主機進程可能會多次關閉或重新啟動。

解決方案是配置運行時,以在工作流空閒時將其卸載。在這種情況下,宿主程序將使用以下代碼初始化運行時:

WorkflowRuntime wr = new WorkflowRuntime();wr.StartRuntime();

用戶還可以在 WorkflowRuntime 類上設置幾個事件處理程序,以便在工作流空閒、持續或被卸載時運行某些代碼。以這種方法配置的運行時,將在工作流遇到 Listen 活動或進入等待狀態時,將工作流自動序列化存入到存儲媒體中。另一個方法是指示宿主程序以編程方式將工作流保持在已知的執行點處。在這種情況下,可以調用 WorkflowInstance 類的 EnqueueItemOnIdle 方法。用戶將獲得 WorkflowInstance 類的實例,作為運行時類的 CreateWorkflow 方法的返回值:

WorkflowInstance inst = theRuntime.CreateWorkflow(type);
...
inst.EnqueueItemOnIdle();
inst.Unload();

但是請記住,調用 EnqueueItemOnIdle 不一定會立即保持工作流。只有工作流處於空閒或掛起狀態時才立即保持。否則,當工作流處於掛起或空閒狀態時,運行時會予以注意並在以後保持工作流實例。請注意,EnqueueItemOnIdle 僅限於保存工作流實例的狀態,不會將其從內存中卸載。要卸載工作流實例,需要調用 Unload 方法。工作流運行時支持的一種標准運行時服務是工作流持久性服務。可以通過 圖 5 中的代碼將其打開。

Figure 5 Workflow Persistence Service

private WorkflowRuntime InitWorkflowRuntime()
{
  // Get a new workflow runtime
  WorkflowRuntime wr = new WorkflowRuntime();
  // Add custom HelpDesk service
  theHelpDeskService = new HelpDeskService();
  wr.AddService(theHelpDeskService);
  // Add system SQL state service
  SqlWorkflowPersistenceService stateService =
    new SqlWorkflowPersistenceService("Data Source=localhost;" +
    "Initial Catalog=WFState;UID=...;");
  wr.AddService(stateService);
  // Start
  wr.StartRuntime();
  return wr;
}
private void btnCreate_Click(object sender, EventArgs e)
{
  // Fill the Parameters collection for this instance of the workflow
  Dictionary<string, object> parameters =
    new Dictionary<string, object>();
  parameters.Add("TicketDescription", txtDesc.Text);
  parameters.Add("TicketUser", txtUser.Text);
  parameters.Add("TicketCreatedBy", txtCreatedBy.Text);
  // Get the type of the workflow
  Type type = typeof(MySamples.HelpDeskWorkflow);
  // Start the workflow instance
  WorkflowInstance inst =
    theWorkflowRuntime.CreateWorkflow(type, parameters);
  // Feedback to the user
  string msg = String.Format("The ticket '{0}' has been successfully " +
    "created!", inst.InstanceId);
  MessageBox.Show(msg, "HelpDesk", MessageBoxButtons.OK,
    MessageBoxIcon.Information);
  FillGrid();
}

圖 5

WorkflowPersistence 服務將序列化功能添加到在給定運行時引擎內執行的所有工作流實例。工作流服務核心功能由 WorkflowPersistenceService 類表示。Windows Workflow Foundation 通過 SqlWorkflowPersistenceService 實現它。此類可以將工作流數據保存到具有已知架構的 SQL Server 2000 或 SQL Server 2005 數據庫中。用戶可以使用許多腳本和基於對話框的實用程序輕松創建目標數據庫。可以從 Windows Workflow Foundation 網站 下載所需內容。必須創建 SQLWorkflowPersistenceService 的實例並向運行時注冊,才能實際啟動運行時。持久性服務的構造函數使用連接字符串作為其唯一的參數。

如前所述,運行時服務是整體 Windows Workflow Foundation 體系結構的可插入部分,因此用戶可以創建自己的服務,並使用自己的服務替換標准服務。

恢復工作流實例

在所述方案中,需要由其他接線員(技術人員)接手任何開放的票證,並盡可能去解決它們或將它們提交給上一級。技術人員將使用其他應用程序,或者至少使用圖 4 中的應用程序的其他實例。在這兩種情況下,都必須通過持久性支持創建工作流運行時的其他實例,並且必須加載和恢復正確的工作流狀態。如您所見,只有在已經保存空閒工作流的 ID 的情況下才發生這種情況。在技術支持示例應用程序中,票證 ID 特意設置為與工作流 ID 相匹配,每個票證都對應於被創建用於處理票證的工作流的一個實例。

圖 6 問題規劃求解

問題規劃求解應用程序(請參閱圖 6)通過使用與圖 4 中的技術支持前端應用程序幾乎相同的代碼來初始化工作流運行時。唯一的微小差別在於在問題規劃求解應用程序中,我是在運行時上為 WorkflowLoaded 和 WorkflowCompleted 事件注冊處理程序:

WorkflowRuntime wr = new WorkflowRuntime();
wr.WorkflowLoaded += OnWorkflowLoaded;
wr.WorkflowCompleted += OnWorkflowCompleted;

接線員從顯示的列表中選擇票證,並與提出票證的用戶協同工作。完成後,她將進行單擊以解決票證或升級票證,從而終止工作流。事件處理程序將基於票證 ID 檢索保存的工作流實例,然後將其從空閒狀態喚醒。以下是用戶所需的代碼:

string workflowID = (string)ticketList.SelectedValue;
Guid id = new Guid(workflowID);
Type type = typeof(MySamples.HelpDeskWorkflow);
try {
WorkflowInstance inst = theWorkflowRuntime.GetLoadedWorkflow(id);
theHelpDeskService.RaiseCloseTicketEvent(inst.InstanceId);
} catch { ... }

前面代碼的核心部分為調用 GetLoadedWorkflow 方法。GetLoadedWorkflow 采用表示工作流實例的 GUID,如果當前內存中沒有 GUID,則從已配置的持久媒體中進行檢索。在這種情況下,工作流將加載到內存中,並被安排執行時間。即使工作流實例以前已中止,仍會發生這種情況。工作流加載回內存後,WorkflowLoaded 事件將激發。

GetLoadedWorkflow 將返回 WorkflowInstance 類型的對象,用戶可以使用該類對象檢查工作流的當前狀態及其活動。此時,將激發工作流等待的一個事件。相應的 EventSink 活動將捕獲事件並繼續,直至工作流結束或遇到後續等待點。

工作流完成時,將自動從 Windows Workflow Foundation 持久性數據庫中刪除。當工作流結束時,WorkflowCompleted 事件將激發。在技術支持方案中,工作流將在接線員單擊解決或升級之後完成(請參閱圖 6)。

從開發的角度而言,關鍵要注意以下兩點。第一,要將事件引發到工作流,必須將申請發布到池線程:

public void RaiseCloseTicketEvent(Guid instanceId) {
ThreadPool.QueueUserWorkItem(JustCloseTheTicket,
new HelpDeskTicketEventArgs(instanceId, "Jim"));
}
public void JustCloseTheTicket(object o) {
HelpDeskTicketEventArgs args = o as HelpDeskTicketEventArgs;
if (TicketClosed != null) TicketClosed(null, args);
}

第二,不能在主 Windows 窗體線程上引發 WorkflowCompleted 事件,因此不能直接更新任何 UI 控件。(這是由於 Windows 窗體控件不是線程安全控件。)確實可以在創建 Windows 窗體控件的線程之外的線程中更新 Windows 窗體控件,但是需要間接調用更新控件的方法:

private delegate void UpdateListDelegate();private void UpdateList() {
this.Invoke(new UpdateListDelegate(FillList));
}

可以從 WorkflowCompleted 事件處理程序調用 UpdateList。Form 類的 Invoke 方法可以確保在正確線程上調用 FillList,以便能夠安全地刷新所有控件。

結論

Windows Workflow Foundation 使用戶可以直觀地設計復雜的算法,從而解決業務問題並為進程建模。工作流是用於說明數據和操作流的工具。因此,任何需要 IF 或 WHILE 語句的方案都可以是工作流。但是,任何人都不能使用僅包含 IF 語句的工作流,工作流運行時確實具有成本,該成本可以在流復雜性超出給定阈值時分攤。

如何界定工作流的使用是否高效低耗的界限?在本專欄實現的技術支持方案對於工作流可能過於簡單,可以通過使用票證數據庫(甚至是工作流通過技術支持服務處理的同一票證數據庫)以等效的方式實現。

基於工作流的解決方案的真正優點是使復雜進程更易於建模和實現,更重要的是使其更易於改進和擴展。Windows Workflow Foundation 為 Windows Workflow 程序提供了托管執行環境,還為程序提供了持續時間、可靠性、掛起/恢復以及補償特征。在某種意義上,活動類似於中間語言 (IL) 操作碼或程序語句,但包含特定領域的知識。簡而言之,Windows Workflow Foundation 使程序語義具有聲明性並且十分准確,使用戶能夠為接近實際進程的應用程序建模。它是最適合此工作的工具。用戶無需使用 IL 編寫前端可視應用程序,而是使用 RAD 開發工具和更具人類可讀性的語言。Windows Workflow Foundation SDK 提供了廣泛的編程語言,專門用於為復雜的業務程序建模,特別是在這些程序可能隨著時間而改進的情況下。在這種情況下,主要好處在於用戶可以添加特殊活動來進行工作流,並使用內置行為活動來控制該工作流。用戶可以專注於任務,其他事情由運行時進行處理。如果要進一步了解 Windows Workflow Foundation 或查找其他信息,請訪問 Windows Workflow Foundation。

本文配套源碼

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