async...await在tcp通訊中的正確用法

2022-08-12 18:05:33
  • 引言

    程式設計能力在不斷的總結中進步以及成長,最近的半年裡,對之前的開源專案程式碼進行迴歸,在重構的過程中進行了很多思考,很多次都想放棄重構,畢竟一個已經在使用的專案,重構基礎程式碼就相當於重新開發了,不過最終還是下定了決心,畢竟重構就是一個成長過程,要想進步,就要不斷的發現原有程式碼的不足,使用新的思維去優化原來的東西,在重構的過程中,針對tcp網路通訊,我有了新的思路。

  • 簡介

    無論是網上的tcp範例或者書本的範例,只要是非同步方式,讀寫基本都是分離的,也就是說如果想通過tcp去和伺服器端進行資料互動,你在A方法傳送了指令,只能在B方法接收並進行處理,這種傳送與接收資料不在同一個方法內,嚴重干擾了程式碼的順序執行邏輯,我們需要將傳送前和接收後的程式碼寫在不同的地方,而現在,我們可以寫在一起。例如:
    

long fileId = 1001;
// 通過tcp傳送命令到伺服器端
bool result = await tcp.Delete(fileId);
// 返回結果後處理相應的業務
if(result){
  // 重新整理資料  
}
else{
  // 提示錯誤
}

    沒錯,這樣子看上去和呼叫http請求一模一樣,是不是邏輯變得簡單了?

  • 呼叫範例

    畫一個時序圖吧,我們來看一下呼叫過程

 

 

 

     從上圖我們可以看出,和其它網上範例的不同在於,我們自己加了一個task管理模組,我們通過自己對Task進行管理,實現了tcp的非同步呼叫但順序執行程式碼。

    將以上範例用在實際程式碼上,效果如下:

 

 

 

 

 

 

  • 核心程式碼

    

/// <summary>
        /// 等待請求
        /// </summary>
        /// <param name="token">請求的token</param>
        /// <param name="timeOut">超時時間</param>
        /// <returns></returns>
        public async Task<T> Wait(T1 token, TimeSpan timeOut)
        {
            TaskCompletionSource<T> taskCompletionSource = new TaskCompletionSource<T>();
            // 將等待結果的任務加入字典中
            TaskDict.Add(token, taskCompletionSource);
            try
            {
                T ret = await taskCompletionSource.Task.WaitAsync(timeOut);
                return ret;
            }
            catch
            {
                // 如果超時,則移除字典中的任務,並丟擲超時異常
                if (TaskDict.ContainsKey(token))
                {
                    TaskDict.Remove(token);
                }
                throw;
            }
        }    

  

  • 結尾

    async...await是非同步方案的一種,配合TaskCompletionSource可以將分散於2個不同地方的程式碼合併到一起,對簡化程式碼邏輯來說還是比較靠譜的。

    目前這種非同步方案已在自己的開源專案(Wireboy.Socket.P2PSocket)中使用了,並且也將此方案適配在了公司專案,主要運用於使用命名管道的程序間通訊。總的來說還是一個比較不錯的方案。

  • 適用場景
  1. tcp通訊
  2. udp通訊
  3. 命名管道通訊
  4. UI執行緒需要大量計算,將計算過程使用執行緒啟動,並通過此方案返回結果。