DotNetty 自己是一個優秀的網絡通信框架,不過它是基於異步事件驅動來處理另外一端的響應,須要在單獨的 Handler 去處理相應的返回結果。而在咱們的實際使用當中,尤爲是 客戶端程序 基本都是 請求-響應 模型,在發送了數據時候須要等待服務器的響應才能進行下一步操做,若是服務器返回的是錯誤信息,則須要進行特殊的處理。html
相似於下面這種方式:git
public async void Button1_Click() { var result = await DotNettyClient.SendData("Hello"); if(result == "Error") { throw new Exception("服務器返回錯誤!"); } Console.WriteLine($"Hello {result}"); }
參閱了大部分資料以後,發如今 Java 的 Netty 當中可使用 Future / Promise 來實現,那麼 C# 是否有相似的組件呢?答案是有的,他們對應的就是 Task
和 TaskCompletionSource
,前者是給調用者的任務,然後者則是用於設置響應任務的結果。github
那麼咱們就能夠這麼來處理,當客戶端發送請求時,附帶惟一的一個請求 ID,並將 TaskCompletionSource
放在一個請求字典當中,請求 ID 做爲字典的 Key,值是 TaskCompletionSource
,以後返回一個 Task
。當客戶端接收到服務器響應的時候,經過 TaskCompletionSource
設置以前那個 Task
的結果,這樣咱們接收到響應以後,就會從以前 await 的地方繼續執行。服務器
這裏我本身的需求僅僅是相似於 同步阻塞式 的操做,因此我直接使用一個隊列來作簡單處理,並無用惟一的請求 ID 來表示不一樣的請求,也沒有使用字典,由於我能夠 保證在同一時間內有且僅有一個客戶端請求被髮起,並且也作了響應的超時處理機制。網絡
實現起來超級簡單,只須要在發起請求的時候,建立一個 TaskCompletionSource<TResponse>
對象。這個泛型參數指的是你想要的返回值類型,這裏我以 TResponse 代替,下面的 DEMO 我會用 string
類型進行演示。框架
建立好一個 TaskCompletionSource<TResponse>
以後,在發送方法裏面,咱們能夠將其對象放在一個先進先出的隊列當中,而後將其 Task
屬性做爲發送方法的返回值。異步
咱們再來處處理服務器響應的 Handler 當中,從隊列裏面拿去以前存放的 TaskCompletionSource<TResponse>
對象,調用其 SetResult()
方法,將具體響應進行設置。async
經過以上的操做,咱們在發送數據的時候,就可使用 await
關鍵字等待服務端的響應,但不會阻塞線程,當客戶端接收到服務端響應時,就會恢復到以前 await
的位置繼續執行。ide
數據發送方法:線程
public static class DotNettyClient { static DotNettyClient() { RequestQueue = new Queue<TaskCompletionSource<string>>(); } public static Queue<TaskCompletionSource<string>> RequestQueue { get; set; } public static async Task<string> SendData(string data) { var resultTask = new TaskCompletionSource<string>(); var buffer = new Unpooled.Buffer(); buffer.WriteBytes(Encoding.UTF8.GetBytes(data)); await _clientChannel.WriteAndFlushAsync(buffer); RequestQueue.Enqueue(resultTask); return await resultTask.Task; } }
服務端響應處理:
public class ProtocolHandler : ChannelHandlerAdapter { public override void ChannelRead(IChannelHandlerContext context, object message) { if(message is string response) { if(!DotNettyClient.RequestQueue.TryDequeue(out TaskCompletionSource<string> result)) return; result.SetResult(response); } } }
這裏我就再也不編寫解析器,主要說明一下代碼的思路,下面在使用的時候就如同第一節說的同樣,直接使用 await
關鍵字等待響應結果便可。
在這裏我並無展現多個異步請求的狀況,若是是用戶同時發起多個請求的時候,你能夠經過數據的惟一 ID 來標識每個請求,讀取時根據惟一 ID 從字典獲取數據,這樣在接收服務端響應的時候就能處理這種狀況了。