2010-03-08 63 views
9

我有一個Windows服務,需要一堆文件的名稱,並對它們進行操作(zip/unzip,更新db等)。根據發送到服務的文件的大小和數量,操作可能需要一些時間。WCF Windows服務 - 長操作/調用模塊的回調

(1)向此服務發送請求的模塊會等待,直到處理完文件。我想知道是否有辦法在服務中提供回調,以便在處理完文件後通知調用模塊。請注意,多個模塊可以一次調用服務來處理文件,因此服務需要提供某種TaskId,我猜。 (2)如果一個服務方法被調用並且正在運行,並且另一個調用被執行到同一個服務,那麼將如何處理該調用(我認爲只有一個線程與服務關聯)。我已經看到,當服務花費時間處理方法時,與服務相關的線程開始增加。

回答

14

WCF的確提供了雙工綁定,允許您指定回調協議,以便服務可以回調到客戶端進行通知。

但是,在我看來,這種機制是相當片狀,並不真正被推薦。

在這種情況下,當調用導致一個相當長的運行操作的情況發生,我會做這樣的事情:

如果你想堅持到HTTP/NetTcp綁定,我想:

  • 丟掉服務的請求,然後「放開」 - 這將是一個單向的呼叫,你只是放棄你想做的事情,然後你的客戶端完成
  • 有一個狀態調用客戶可以在給定時間後打電話查詢請求的結果現在是否已準備就緒
  • 如果是這樣,應該有第三個服務調用來檢索結果

所以你的情況,你可以下車來壓縮一些文件的請求。該服務將關閉並完成其工作並將產生的ZIP存儲在臨時位置。然後稍後客戶端可以檢查ZIP是否準備就緒,如果是,請檢索它。

這工作更好地通過消息隊列(MSMQ),它存在於每個Windows服務器計算機(但不是很多人似乎知道它或使用它):

  • 客戶脫落在請求隊列請求
  • 服務偵聽該請求隊列和請求後獲取的請求並執行它的工作原理
  • 的服務的話,可以後的結果到結果隊列,在這你又來電者聽

查看如何通過閱讀優秀的MSDN文章Foudnations: Build a queue WCF Response Service高效地完成所有這些 - 強烈推薦!

在我看來,基於消息隊列的系統往往更穩定和更容易出錯,基於雙工/回調合同的系統。

+0

因爲這個Windows服務運行用戶的系統上,我不能使用MSMQ(XP,Vista中,7),是產品的一部分用戶下載。所以GetStatus()是我想要的方式。想知道在多個命令同時執行時會有多複雜。 – A9S6 2010-03-08 10:36:33

+0

@ A9S6:WCF爲每個調用創建一個全新的服務實例 - 因此,多個請求在同一時間確實沒有問題 - 每個都有自己的小型隔離服務類實例。 – 2010-03-08 11:19:06

+0

您是否意味着在每次調用時都會創建Service Class(ServiceBase繼承)的新實例?我以爲這隻發生在Web服務上,而不是Windows服務。那麼,除非每個方法調用都需要,否則不應該在這個類中放置初始化? – A9S6 2010-03-08 13:39:46

1

(1)實現此目的的最簡單方法是使用taskId,然後使用另一種稱爲IsTaskComplete的方法,客戶端可以使用該方法檢查任務是否已完成。

(2)對服務進行的其他調用將啓動新線程。

編輯:默認的服務行爲是每次調用啓動新線程。可配置屬性爲Instance Context Mode,可以設置爲PerCall,PerSession或Shareable。

+0

你確定(2)嗎?有沒有解釋這種行爲的文章或關閉這些服務線程的方法? – A9S6 2010-03-08 10:38:11

0

這個問題有一個解決方案,但我使用WCF雙工服務來獲得長操作的結果,即使我發現一個問題花了我幾個小時來解決(這就是爲什麼我搜索這個問題較早),現在它完美地工作,我相信這是WCF雙工服務框架內的一個簡單解決方案。

長時間操作有什麼問題?主要的問題是在服務器執行操作時阻塞客戶端接口,並且使用WCF雙工服務,我們可以使用回調客戶端來避免阻塞(這是一種避免阻塞的舊方法,但它可以很容易地轉換爲使用TaskCompletionSource的異步/等待框架)。

簡而言之,解決方案使用一種方法在服務器上異步啓動操作並立即返回。結果準備就緒後,服務器通過客戶端回撥返回它們。

首先,你必須遵循任何標準指南來創建WCF雙工服務和客戶端,我發現這兩個有用:

msdn duplex service

Codeproject Article WCF Duplex Service

然後按照以下步驟將自己的代碼:

  1. 定義帶有事件管理器方法的回調接口以從t他服務器並在客戶端接收它們。

    public interface ILongOperationCallBack 
    { 
        [OperationContract(IsOneWay = true)] 
        void OnResultsSend(....);   
    } 
    
  2. 定義服務接口與方法傳遞由長操作所需的參數(參照在CallBackContractAttribute先前ILongOperationCallBack接口)

    [ServiceContract(CallbackContract=typeof(ILongOperationCallBack))] 
    public interface ILongOperationService 
    { 
        [OperationContract] 
        bool StartLongOperation(...); 
    } 
    
  3. 在實現該服務的服務類接口,首先獲取客戶端回調的代理並將其保存在類字段中,然後異步啓動長時間操作並立即返回bool值。長操作工作完成後,使用客戶端回撥代理字段將結果發送給客戶端。

    public class LongOperationService:ILongOperationService 
    { 
        ILongOperationCallBack clientCallBackProxy; 
        public ILongOperationCallBack ClientCallBackProxy 
        { 
         get 
         { 
          return OperationContext.Current.GetCallbackChannel<ITrialServiceCallBack>()); 
         } 
        } 
    
        public bool StartLongOperation(....) 
        { 
         if(!server.IsBusy) 
         { 
          //set server busy state 
          //**Important get the client call back proxy here and save it in a class field.** 
          this.clientCallBackProxy=ClientCallBackProxy; 
          //start long operation in any asynchronous way 
          ......LongOperationWorkAsync(....) 
          return true; //return inmediately 
         } 
         else return false; 
        } 
    
        private void LongOperationWorkAsync(.....) 
        { 
         .... do work... 
         //send results when finished using the cached client call back proxy 
         this.clientCallBackProxy.SendResults(....); 
         //clear server busy state 
        } 
        .... 
    } 
    
  4. 在客戶端創建一個實現ILongOperationCallBack接受結果,並添加了一個方法在服務器(start方法和事件管理器啓動運行時間長的一類並不需要在同一類)

    public class LongOperationManager: ILongOperationCallBack 
    { 
    
        public busy StartLongOperation(ILongOperationService server, ....) 
        { 
         //here you can make the method async using a TaskCompletionSource 
         if(server.StartLongOperation(...)) Console.WriteLine("long oper started"); 
         else Console.Writeline("Long Operation Server is busy") 
        } 
    
        public void OnResultsSend(.....) 
        { 
         ... use long operation results.. 
         //Complete the TaskCompletionSource if you used one 
        } 
    } 
    

注:

  1. 我用的是布爾RET urn在StartLongOperation方法中指示服務器是Busy而不是Down,但只有在長操作不能像我的實際應用程序中那樣併發時才需要,並且可能在WCF中有最佳方式來實現非併發(發現服務器是否關閉,像往常一樣添加一個Try/Catch塊)。

  2. 我沒有看到記錄的重要報價是需要在StartLongOperation方法中緩存回調客戶端代理。我的問題是我試圖在工作方法中獲取代理(是的,所有示例都在服務方法中使用回調客戶端代理,但在文檔中沒有明確說明,並且在長時間的操作我們必須延遲迴撥直到操作結束)。

  3. 在服務方法返回並且在下一個服務方法之前,不要獲取並緩存兩次回調代理。

聲明:我還沒有添加代碼來控制誤差等