發(fā)布日期: 2006-4-28 | 更新日期: 2006-4-28
下載本文的代碼:
CuttingEdge0603.exe (663KB)
本頁內(nèi)容
常見業(yè)務(wù)方案應(yīng)用 Windows Workflow Foundation 技術(shù)支持解決方案人為因素技術(shù)支持前端狀態(tài)持久性 恢復(fù)工作流實(shí)例結(jié)論在 2006 年 1 月號中,Don Box 和 Dharma Shukla 介紹了 Windows® Workflow Foundation,并討論了框架的整體體系結(jié)構(gòu)及其構(gòu)成組件(請參閱
WinFX Workflow:Simplify Development With The Declarative Model Of Windows Workflow Foundation [英文])。這篇文章促使我想進(jìn)一步討論這個(gè)主題,并介紹如何使用 Windows Workflow Foundation 來處理自動(dòng)進(jìn)程與人工活動(dòng)貫穿相交的這種常見業(yè)務(wù)方案。它為開發(fā)和執(zhí)行基于復(fù)雜過程的多種應(yīng)用程序提供了框架。典型示例包括文檔管理應(yīng)用程序、企業(yè)對企業(yè)應(yīng)用程序和企業(yè)對消費(fèi)者應(yīng)用程序。用戶可以使用 Visual Studio® 2005 幫助設(shè)計(jì)基礎(chǔ)工作流以及有關(guān)的頂級應(yīng)用程序和程序集。
常見業(yè)務(wù)方案
對于訂單處理、采購申請、差旅費(fèi)用之類的任務(wù),各組織通常會設(shè)有許多內(nèi)部進(jìn)程。工作流使這些獨(dú)立進(jìn)程以透明、動(dòng)態(tài)、有效的方式按順序進(jìn)行。
讓我們看一個(gè)典型的技術(shù)支持工作流進(jìn)程。在技術(shù)支持人員接到客戶電話,并開立記錄客戶姓名、來電時(shí)間和問題簡要說明的票證時(shí),進(jìn)程即算開始。創(chuàng)建票證之后,該技術(shù)支持人員會將這件事放在一邊,并等待其他來電。下班時(shí),他將注銷計(jì)算機(jī),然后回家。此時(shí),在另一個(gè)部門中(可能位于其他城市),一組技術(shù)人員正專注解決這些未解決的問題。每個(gè)工作的技術(shù)人員要選取申請,然后解決該申請或?qū)⒃撋暾埳壍降诙墡椭?。如何編寫代碼來實(shí)現(xiàn)此進(jìn)程?
可以使用 Windows 窗體應(yīng)用程序來收集電話的相關(guān)輸入數(shù)據(jù),并在數(shù)據(jù)庫中創(chuàng)建一個(gè)記錄:即包含時(shí)間、說明、狀態(tài)和唯一 ID 的票證。第二個(gè) Windows 窗體應(yīng)用程序的用戶將看到待處理申請的實(shí)時(shí)列表,然后選取一個(gè)申請。然后,接線員將盡量解決問題(回電給客戶、檢索申請的信息、發(fā)送電子郵件或執(zhí)行一些遠(yuǎn)程活動(dòng)),并指明問題是已解決還是需要進(jìn)一步研究。此決策可由一個(gè)命令性操作表示,例如單擊某個(gè)按鈕更新同一基礎(chǔ)數(shù)據(jù)庫中的票證。最后,如果還涉及其他類別的用戶,自定義前端將使這些用戶能夠指明問題已成功關(guān)閉或中止。
盡管此過程明確地表達(dá)了工作流需要用戶進(jìn)行某些決策,但可以使用以標(biāo)準(zhǔn)編程語言和數(shù)據(jù)庫編寫的傳統(tǒng)順序代碼輕松實(shí)現(xiàn)。
返回頁首應(yīng)用 Windows Workflow Foundation
如果用戶具備由各活動(dòng)組成的基于工作流的系統(tǒng)(如 Windows Workflow Foundation),則可利用命令性代碼和聲明性活動(dòng)地圖的強(qiáng)大組合以及綁定它們的聲明性規(guī)則來實(shí)現(xiàn)應(yīng)用程序。主要好處在于用戶可以為解決方案建模(甚至以直觀方式建模),將 Windows Workflow 嵌入運(yùn)行時(shí)服務(wù)器來解釋圖表,并使 Windows Workflow 遵循在創(chuàng)建塊中定義的鏈接。進(jìn)程越復(fù)雜,為其設(shè)計(jì)和實(shí)現(xiàn)的流程就越簡單。進(jìn)程動(dòng)態(tài)更改越容易,用戶需要編寫和維護(hù)的代碼數(shù)量就越少。讓我們了解一下如何實(shí)現(xiàn)技術(shù)支持方案的 Windows Workflow Foundation 解決方案。
返回頁首技術(shù)支持解決方案
通過創(chuàng)建票證,創(chuàng)建的技術(shù)支持工作流程即開始,然后在等待連接用戶或技術(shù)人員給予響應(yīng)時(shí)停止。無論票證是關(guān)閉還是升級,工作流都將獲得外部事件,并更新應(yīng)用程序的內(nèi)部狀態(tài)以跟蹤該事件。因此,工作流需要與外界進(jìn)行交互。這類異步活動(dòng)是 Windows Workflow Foundation 解決的實(shí)際工作流進(jìn)程的固有問題之一。因?yàn)樾枰c系統(tǒng)外部的實(shí)體進(jìn)行交互,所以宿主應(yīng)用程序和工作流可以定義約定,以進(jìn)行任何必要的數(shù)據(jù)交換。此處顯示的 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);}
現(xiàn)在,開始編寫名為 HelpDeskService 的服務(wù)類,如
圖 1 所示。HelpDeskService 類將添加到工作流運(yùn)行時(shí),表示宿主應(yīng)用程序和工作流之間的接觸點(diǎn)。宿主應(yīng)用程序?qū)⒄{(diào)用該類的公共方法以激發(fā)對工作流的外部事件。激發(fā)的事件會以信號形式通知將指導(dǎo)操作流的特定于域的事件。IHelpDeskService 接口上的方法僅表示工作流可以通過 InvokeMethod 活動(dòng)在外部組件上調(diào)用的部分代碼。以此方法計(jì)算出的 HelpDeskService 的邏輯能夠在很大程度上增加工作流的靈活性,因?yàn)樗旧峡梢允构ぷ髁骰卣{(diào)宿主,以真正實(shí)現(xiàn)基本操作。通過包含實(shí)現(xiàn) IHelpDeskService 接口的類,不同宿主可以使用不同算法和存儲媒體創(chuàng)建、解決或升級票證,同時(shí)保持工作流邏輯不變。用戶可以在其他程序集中編譯接口和類,也可以僅將這些文件保留在定義工作流的同一程序集中。
HelpDeskWorkflow 是 Windows 窗體應(yīng)用程序駐留的順序工作流。圖 2 顯示的是其圖形模型。它開始于 InvokeMethod 活動(dòng),該活動(dòng)導(dǎo)致應(yīng)用程序基于用戶提供的信息創(chuàng)建票證。然后,票證將放置在數(shù)據(jù)庫中,等待技術(shù)人員選取,以進(jìn)行解決或升級。
圖 2 Visual Studio 2005 中的技術(shù)支持工作流
創(chuàng)建票證之后,工作流將進(jìn)入等待狀態(tài),可能會等待很長時(shí)間 - 甚至幾小時(shí)或幾天。這段時(shí)間內(nèi)會出現(xiàn)什么情況?是否應(yīng)該將工作流實(shí)例加載到內(nèi)存中?如果工作流空閑,可以將其從內(nèi)存中卸載 - 這一進(jìn)程稱為“鈍化”。本地服務(wù)(如技術(shù)支持服務(wù))保留宿主應(yīng)用程序和休眠工作流之間的主要接觸點(diǎn)。
返回頁首人為因素
當(dāng)人為因素與宿主應(yīng)用程序進(jìn)行交互并執(zhí)行一些操作來喚醒工作流時(shí),本地服務(wù)將向運(yùn)行時(shí)發(fā)布請求來恢復(fù)鈍化的工作流。Windows Workflow Foundation 工具箱包含一個(gè)稱為 Listen 的活動(dòng),該活動(dòng)只是使工作流空閑并偵聽傳入的喚醒呼叫。圖 2 中標(biāo)有 WaitForSolution 的塊是 Listen 活動(dòng)的實(shí)例。
如果沒有一個(gè)或多個(gè)子分支(每個(gè)分支表示一個(gè)可掛接在此點(diǎn)的可能發(fā)生的事件),Listen 活動(dòng)基本上沒有意義。在該技術(shù)支持示例中,Listen 活動(dòng)包含兩個(gè)分支 - 票證關(guān)閉和票證升級。每個(gè)分支都是一個(gè) EventDriven 活動(dòng)。Listen 活動(dòng)和 EventDriven 活動(dòng)都不需要特別設(shè)置,它們只是子活動(dòng)的容器。要捕獲外部事件,工作流需要 EventSink 組件。
在圖 2 中,TicketClosed 塊是綁定到本地技術(shù)支持服務(wù)中的 TicketClosed 事件的 EventSink。圖 3 列出了此處設(shè)置的 EventSink 屬性。首先,請選擇提供事件的服務(wù)接口。必須設(shè)置 InterfaceType 實(shí)現(xiàn),此外,僅能選擇以 [DataExchangeService] 屬性修飾的接口,如
圖 1 所示。
圖 3 EventSink 屬性
設(shè)置數(shù)據(jù)交換接口后,將使用接口中找到的所有事件預(yù)填充 EventName 屬性。用戶選取選擇的事件并設(shè)置其參數(shù)。如果希望在事件得到處理后得到進(jìn)一步的通知,還要設(shè)置 Invoked 屬性。
在等待人為干預(yù)的一段空閑時(shí)間之后,EventSink 將控制任務(wù)返回給工作流。事件發(fā)生后,繼續(xù)進(jìn)行與工作流相應(yīng)的其他任何活動(dòng)。例如,在技術(shù)支持應(yīng)用程序中調(diào)用本地服務(wù)的另一個(gè)方法,以更新票證數(shù)據(jù)庫中的票證狀態(tài)。
在實(shí)際情況下,工作流至少需要與一個(gè)數(shù)據(jù)庫直接或間接交互。在此示例中,使用的是 SQL Server™ 2000 表 Tickets,其中每行都對應(yīng)于一個(gè)正在處理的票證。票證 ID 特意設(shè)置為與用于管理票證的工作流 ID 相匹配。
返回頁首技術(shù)支持前端
工作流經(jīng)編譯后,只是一個(gè)可重用的程序集,因此可以被任何類型的基于 .NET 的應(yīng)用程序引用。現(xiàn)在,讓我們?yōu)榧夹g(shù)支持接線員來構(gòu)想一個(gè)前端應(yīng)用程序。該應(yīng)用程序是 Windows 窗體程序,允許用戶填充表單,并創(chuàng)建票證以啟動(dòng)工作流,如圖 4 所示。
圖 4 技術(shù)支持前端應(yīng)用程序
工作流調(diào)用本地服務(wù)的 CreateTicket 方法,并使服務(wù)在票證數(shù)據(jù)庫中添加新記錄。在接線員開立新票證之后,工作流將開始并進(jìn)入空閑狀態(tài)等待人為干預(yù),而該接線員將繼續(xù)接聽電話并回復(fù)電子郵件。每個(gè)票證都由工作流的不同實(shí)例來表示。為了方便起見,工作流 ID 也是票證的 ID。
返回頁首狀態(tài)持久性
在一天結(jié)束的時(shí)候,工作流成為一個(gè)活動(dòng)樹,如何管理其保留時(shí)間?Windows Workflow Foundation 運(yùn)行時(shí)引擎會管理所有工作流的執(zhí)行,使工作流長時(shí)間保持活動(dòng)狀態(tài),甚至在重新啟動(dòng)計(jì)算機(jī)時(shí)也不會受到影響。運(yùn)行時(shí)引擎由可插拔式服務(wù)提供支持,這些服務(wù)提供了完整豐富的執(zhí)行環(huán)境 - 事務(wù)、持久性、跟蹤、計(jì)時(shí)器和線程。
除非有某些干預(yù)使工作流繼續(xù)進(jìn)行,否則工作流不能保留在主機(jī)進(jìn)程的內(nèi)存中。如前所述,許多實(shí)際基于人為干預(yù)的工作流可能需要等待幾個(gè)小時(shí)(甚至更長時(shí)間)才能繼續(xù)。在前面的示例中,技術(shù)支持接線員啟動(dòng)了工作流,并在前端應(yīng)用程序進(jìn)程中加載了工作流類。然后,該接線員就可以關(guān)閉計(jì)算機(jī)回家了。但是,票證必須保持激活狀態(tài),并且對于其他接線員(甚至其他應(yīng)用程序內(nèi)的接線員)可用。因此,工作流必須支持對長效存儲媒體的序列化。
工作流可能是一個(gè)長時(shí)間運(yùn)行的操作,不適用于那些在內(nèi)存中連續(xù)幾天保持激活狀態(tài)的對象。首先,主機(jī)進(jìn)程通常不能為連續(xù)幾天活動(dòng)所積累的所有對象提供緩存;其次,主機(jī)進(jìn)程可能會多次關(guān)閉或重新啟動(dòng)。
解決方案是配置運(yùn)行時(shí),以在工作流空閑時(shí)將其卸載。在這種情況下,宿主程序?qū)⑹褂靡韵麓a初始化運(yùn)行時(shí):
WorkflowRuntime wr = new WorkflowRuntime();wr.StartRuntime();
用戶還可以在 WorkflowRuntime 類上設(shè)置幾個(gè)事件處理程序,以便在工作流空閑、持續(xù)或被卸載時(shí)運(yùn)行某些代碼。以這種方法配置的運(yùn)行時(shí),將在工作流遇到 Listen 活動(dòng)或進(jìn)入等待狀態(tài)時(shí),將工作流自動(dòng)序列化存入到存儲媒體中。另一個(gè)方法是指示宿主程序以編程方式將工作流保持在已知的執(zhí)行點(diǎn)處。在這種情況下,可以調(diào)用 WorkflowInstance 類的 EnqueueItemOnIdle 方法。用戶將獲得 WorkflowInstance 類的實(shí)例,作為運(yùn)行時(shí)類的 CreateWorkflow 方法的返回值:
WorkflowInstance inst = theRuntime.CreateWorkflow(type);...inst.EnqueueItemOnIdle();inst.Unload();
但是請記住,調(diào)用 EnqueueItemOnIdle 不一定會立即保持工作流。只有工作流處于空閑或掛起狀態(tài)時(shí)才立即保持。否則,當(dāng)工作流處于掛起或空閑狀態(tài)時(shí),運(yùn)行時(shí)會予以注意并在以后保持工作流實(shí)例。請注意,EnqueueItemOnIdle 僅限于保存工作流實(shí)例的狀態(tài),不會將其從內(nèi)存中卸載。要卸載工作流實(shí)例,需要調(diào)用 Unload 方法。工作流運(yùn)行時(shí)支持的一種標(biāo)準(zhǔn)運(yùn)行時(shí)服務(wù)是工作流持久性服務(wù)??梢酝ㄟ^
圖 5 中的代碼將其打開。
WorkflowPersistence 服務(wù)將序列化功能添加到在給定運(yùn)行時(shí)引擎內(nèi)執(zhí)行的所有工作流實(shí)例。工作流服務(wù)核心功能由 WorkflowPersistenceService 類表示。Windows Workflow Foundation 通過 SqlWorkflowPersistenceService 實(shí)現(xiàn)它。此類可以將工作流數(shù)據(jù)保存到具有已知架構(gòu)的 SQL Server 2000 或 SQL Server 2005 數(shù)據(jù)庫中。用戶可以使用許多腳本和基于對話框的實(shí)用程序輕松創(chuàng)建目標(biāo)數(shù)據(jù)庫??梢詮?a target="_blank" >Windows Workflow Foundation 網(wǎng)站 下載所需內(nèi)容。必須創(chuàng)建 SQLWorkflowPersistenceService 的實(shí)例并向運(yùn)行時(shí)注冊,才能實(shí)際啟動(dòng)運(yùn)行時(shí)。持久性服務(wù)的構(gòu)造函數(shù)使用連接字符串作為其唯一的參數(shù)。
如前所述,運(yùn)行時(shí)服務(wù)是整體 Windows Workflow Foundation 體系結(jié)構(gòu)的可插入部分,因此用戶可以創(chuàng)建自己的服務(wù),并使用自己的服務(wù)替換標(biāo)準(zhǔn)服務(wù)。
返回頁首恢復(fù)工作流實(shí)例
在所述方案中,需要由其他接線員(技術(shù)人員)接手任何開放的票證,并盡可能去解決它們或?qū)⑺鼈兲峤唤o上一級。技術(shù)人員將使用其他應(yīng)用程序,或者至少使用圖 4 中的應(yīng)用程序的其他實(shí)例。在這兩種情況下,都必須通過持久性支持創(chuàng)建工作流運(yùn)行時(shí)的其他實(shí)例,并且必須加載和恢復(fù)正確的工作流狀態(tài)。如您所見,只有在已經(jīng)保存空閑工作流的 ID 的情況下才發(fā)生這種情況。在技術(shù)支持示例應(yīng)用程序中,票證 ID 特意設(shè)置為與工作流 ID 相匹配,每個(gè)票證都對應(yīng)于被創(chuàng)建用于處理票證的工作流的一個(gè)實(shí)例。
圖 6 問題規(guī)劃求解
問題規(guī)劃求解應(yīng)用程序(請參閱圖 6)通過使用與圖 4 中的技術(shù)支持前端應(yīng)用程序幾乎相同的代碼來初始化工作流運(yùn)行時(shí)。唯一的微小差別在于在問題規(guī)劃求解應(yīng)用程序中,我是在運(yùn)行時(shí)上為 WorkflowLoaded 和 WorkflowCompleted 事件注冊處理程序:
WorkflowRuntime wr = new WorkflowRuntime();wr.WorkflowLoaded += OnWorkflowLoaded;wr.WorkflowCompleted += OnWorkflowCompleted;
接線員從顯示的列表中選擇票證,并與提出票證的用戶協(xié)同工作。完成后,她將進(jìn)行單擊以解決票證或升級票證,從而終止工作流。事件處理程序?qū)⒒谄弊C ID 檢索保存的工作流實(shí)例,然后將其從空閑狀態(tài)喚醒。以下是用戶所需的代碼:
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 { ... }
前面代碼的核心部分為調(diào)用 GetLoadedWorkflow 方法。GetLoadedWorkflow 采用表示工作流實(shí)例的 GUID,如果當(dāng)前內(nèi)存中沒有 GUID,則從已配置的持久媒體中進(jìn)行檢索。在這種情況下,工作流將加載到內(nèi)存中,并被安排執(zhí)行時(shí)間。即使工作流實(shí)例以前已中止,仍會發(fā)生這種情況。工作流加載回內(nèi)存后,WorkflowLoaded 事件將激發(fā)。
GetLoadedWorkflow 將返回 WorkflowInstance 類型的對象,用戶可以使用該類對象檢查工作流的當(dāng)前狀態(tài)及其活動(dòng)。此時(shí),將激發(fā)工作流等待的一個(gè)事件。相應(yīng)的 EventSink 活動(dòng)將捕獲事件并繼續(xù),直至工作流結(jié)束或遇到后續(xù)等待點(diǎn)。
工作流完成時(shí),將自動(dòng)從 Windows Workflow Foundation 持久性數(shù)據(jù)庫中刪除。當(dāng)工作流結(jié)束時(shí),WorkflowCompleted 事件將激發(fā)。在技術(shù)支持方案中,工作流將在接線員單擊解決或升級之后完成(請參閱圖 6)。
從開發(fā)的角度而言,關(guān)鍵要注意以下兩點(diǎn)。第一,要將事件引發(fā)到工作流,必須將申請發(fā)布到池線程:
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 窗體線程上引發(fā) WorkflowCompleted 事件,因此不能直接更新任何 UI 控件。(這是由于 Windows 窗體控件不是線程安全控件。)確實(shí)可以在創(chuàng)建 Windows 窗體控件的線程之外的線程中更新 Windows 窗體控件,但是需要間接調(diào)用更新控件的方法:
private delegate void UpdateListDelegate();private void UpdateList() {this.Invoke(new UpdateListDelegate(FillList));}
可以從 WorkflowCompleted 事件處理程序調(diào)用 UpdateList。Form 類的 Invoke 方法可以確保在正確線程上調(diào)用 FillList,以便能夠安全地刷新所有控件。
返回頁首結(jié)論
Windows Workflow Foundation 使用戶可以直觀地設(shè)計(jì)復(fù)雜的算法,從而解決業(yè)務(wù)問題并為進(jìn)程建模。工作流是用于說明數(shù)據(jù)和操作流的工具。因此,任何需要 IF 或 WHILE 語句的方案都可以是工作流。但是,任何人都不能使用僅包含 IF 語句的工作流,工作流運(yùn)行時(shí)確實(shí)具有成本,該成本可以在流復(fù)雜性超出給定閾值時(shí)分?jǐn)偂?div style="height:15px;">
如何界定工作流的使用是否高效低耗的界限?在本專欄實(shí)現(xiàn)的技術(shù)支持方案對于工作流可能過于簡單,可以通過使用票證數(shù)據(jù)庫(甚至是工作流通過技術(shù)支持服務(wù)處理的同一票證數(shù)據(jù)庫)以等效的方式實(shí)現(xiàn)。
基于工作流的解決方案的真正優(yōu)點(diǎn)是使復(fù)雜進(jìn)程更易于建模和實(shí)現(xiàn),更重要的是使其更易于改進(jìn)和擴(kuò)展。Windows Workflow Foundation 為 Windows Workflow 程序提供了托管執(zhí)行環(huán)境,還為程序提供了持續(xù)時(shí)間、可靠性、掛起/恢復(fù)以及補(bǔ)償特征。在某種意義上,活動(dòng)類似于中間語言 (IL) 操作碼或程序語句,但包含特定領(lǐng)域的知識。簡而言之,Windows Workflow Foundation 使程序語義具有聲明性并且十分準(zhǔn)確,使用戶能夠?yàn)榻咏鼘?shí)際進(jìn)程的應(yīng)用程序建模。它是最適合此工作的工具。用戶無需使用 IL 編寫前端可視應(yīng)用程序,而是使用 RAD 開發(fā)工具和更具人類可讀性的語言。Windows Workflow Foundation SDK 提供了廣泛的編程語言,專門用于為復(fù)雜的業(yè)務(wù)程序建模,特別是在這些程序可能隨著時(shí)間而改進(jìn)的情況下。在這種情況下,主要好處在于用戶可以添加特殊活動(dòng)來進(jìn)行工作流,并使用內(nèi)置行為活動(dòng)來控制該工作流。用戶可以專注于任務(wù),其他事情由運(yùn)行時(shí)進(jìn)行處理。如果要進(jìn)一步了解 Windows Workflow Foundation 或查找其他信息,請?jiān)L問
Dino Esposito 是 Solid Quality Learning 顧問,并且是 Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005) 的作者。Dino 居住在意大利,經(jīng)常就世界范圍內(nèi)的行業(yè)事件進(jìn)行發(fā)言。請通過