2012-08-15 81 views
2

我目前正在實現一個應用程序協議庫依靠TCP/IP傳輸(持久連接)。事件v.s.使用TaskCompletionSource異步方法

我想實現一個很好的異步實現,它依賴於使用C#5 async/await構造的TAP模式,主要是爲了實踐我迄今爲止在理論上只見過的概念。

客戶端可以連接到遠程服務器並向其發送請求。客戶端接收來自服務器的響應以及請求(全雙工模式)。

從視客戶端代碼的角度來看,我的庫中的異步調用將請求發送到服務器並接收所述關聯響應是簡單的:

var rsp = await session.SendRequestAsync(req); 

從我的協議庫的內部,我只是建立請求,將其轉換爲字節(在網絡流上發送),然後我在流上調用WriteAsync,然後等待發送請求之前創建的任務,使用TaskCompletionSource對象,即基本上等待接收相關響應(並在tcs上設置結果),然後將響應返回給客戶端調用者。

這部分看起來不錯。

現在「問題」涉及服務器向客戶端發送請求的部分。服務器可以向客戶端發送不同類型的請求。

我的協議庫正在使用異步循環來偵聽基礎流(接收來自服務器的傳入響應或請求)。 此循環正在讀取流上的異步響應/請求,然後在來自服務器的請求的情況下,它會引發與請求類型相對應的事件(如ReceivedRequestTypeA)。客戶端代碼可以訂閱這些事件以在從服務器接收到特定請求類型時得到通知。它們的事件參數包含與請求相關的所有參數以及由客戶端設置的響應對象,一旦事件處理程序代碼完成,該響應對象將被逐個庫異步發送。

異步偵聽循環的代碼如下。請不要介意while true,不是很漂亮(應該使用取消模式),但這不是重點!

private async Task ListenAsync() 
{ 
    while(true) 
    { 
    Request req = await ReadRequestAsync(); 
    await OnReceivedRequest(req); 
    } 
} 

所以循環調用異步方法ReadRequestAsync這是剛剛讀取一些字節異步流中直到一個完整的請求或響應是可用的。 然後,它把請求轉發到所述異步方法OnReceivedRequest其代碼可以如下所示:

private async Task OnReceivedRequest(Request req) 
{ 
    var eventArgs = new ReceivedRequestEventArgs { Req = req }; 

    if (req is RequestTypeA) 
    { ReceivedRequestTypeA(this, eventArgs); } 

    [... Other request types ...] 

    await SendResponseAsync(eventArgs.Resp); 
} 

這種異步方式提高適當的請求類型的事件。 客戶端代碼被訂閱到這個事件,所以它的適當的事件處理程序方法被調用......客戶端代碼做它需要的任何請求,然後構造一個響應並將其設置在EventArgs對象中 - 事件處理程序方法的結束 - 。該代碼在庫中的OnReceivedRequest中繼續,並且異步發送響應(在基礎流上調用WriteAsync)。

我不認爲這是一種好的方法,因爲它可以完全阻止庫中的異步循環,如果客戶端的事件處理程序代碼正在做一個冗長的阻塞操作(拜拜完全異步協議庫,你是現在由於客戶端代碼而變得同步)。如果我正在使用基於異步任務的委託進行事件並等待它,也會發生同樣的情況。

我想,而不是使用事件,我可以有一個異步方法GetRequestTypeAAsync()這將使用庫TaskCompletionSource對象來實現,並且所述TCS結果被設置在OnReceivedRequest該請求。而在客戶端代碼方面,代碼不是訂閱ReceivedRequestTypeA事件,而是由代碼GetRequestTypeAAsync()組成。儘管客戶端代碼必須以某種方式提供一個響應庫發送到服務器,我不知道這是如何工作的...

我的大腦現在完全模糊,不能真正想清楚。任何建議一個不錯的設計將不勝感激。

謝謝!

回答

3

我也在致力於async/await TCP/IP套接字,我強烈建議你看看TPL Dataflow。使用兩個BufferBlocks(一個用於讀取,一個用於寫入)製作async-友好的終端非常容易。

在我的系統中,TCP/IP套接字封裝器公開了一個代表原始讀取的簡單ISourceBlock<ArraySegment<byte>>。然後將其鏈接到執行message framingTransformManyBlock,並從那裏鏈接到將字節分析爲實際消息實例的TransformBlock

如果您有一個RequestType基類,您可以從中繼承所有其他消息類型,此方法效果最佳。然後,您可以有一個接收任務,從數據流管道末尾(異步地)接收RequestType消息實例。

+0

不錯!今天的另一個發現感謝您的回答! 我剛剛觀看了來自Stephen Toub「TPL DataFlow Tour」的15分鐘視頻,它看起來非常整齊。想深入挖掘一下,看看我能如何將它應用到我的上下文中(感謝這些線索!)。順便說一句爲什麼Dataflow不是.NET 4.5的一部分?它以某種方式間接暗示「穩定性」問題? – darkey 2012-08-16 01:26:22

+0

好吧,剛剛發現了我的評論問題的答案 http://blogs.msdn.com/b/bclteam/archive/2012/05/30/mef-and-tpl-dataflow-nuget-packages-for-net- framework-4-5-rc.aspx 再次感謝! – darkey 2012-08-16 01:29:47

+1

或者,如果你想分別處理每個消息類型,每一個都可以是一個單獨的'Action'塊,通過使用['LinkTo()'和謂詞](http:// msdn)鏈接到解析'TrasnsformBlock'。 microsoft.com/en-us/library/hh194832.aspx),它根據消息的類型進行過濾。 – svick 2012-08-16 11:16:59