自己動手寫個非同步IO函數 --(基於 c# Task)

来源:https://www.cnblogs.com/yuanchenhui/archive/2020/02/13/async-io-example.html

前言 對於服務端,達到高性能、高擴展離不開非同步。對於客戶端,函數執行時間是1毫秒還是100毫秒差別不大,沒必要為這一點點時間煞費苦心。對於非同步,好多人還有誤解,如: 非同步就是多線程;非同步就是如何利用好線程池。非同步不是這麼簡單,否則微軟沒必要在非同步上花費這麼多心思。本文就介紹非同步最新的實現方式:Tas ...


前言    對於服務端,達到高性能、高擴展離不開非同步。對於客戶端,函數執行時間是1毫秒還是100毫秒差別不大,沒必要為這一點點時間煞費苦心。對於非同步,好多人還有誤解,如: 非同步就是多線程;非同步就是如何利用好線程池。非同步不是這麼簡單,否則微軟沒必要在非同步上花費這麼多心思。本文就介紹非同步最新的實現方式:Task,並自己動手寫一個非同步IO函數。只有瞭解了非同步函數內部實現方式,才能更好的利用它。

  對於c#,非同步處理經過了多個階段,但是對於現階段非同步就是Task,微軟用Task來抽象非同步操作。以後的非同步函數,處理的都是Task。你會看到處處都是task的身影。為了處理Task,c#引入了兩個關鍵詞async,await。這兩個關鍵詞也可以說是一個關鍵詞,因為async的存在是為了表明await是關鍵詞。總而言之:兩個關鍵詞幹了一件事,async關鍵詞並不改變函數的聲明。

  有人說await就是語法糖,不值得大書特書,我只能說你錯了。軟體開發堅持的原則為:代碼要省,代碼要清晰易懂!如果沒有語法糖,代碼的維護性大大降低。await這個語法糖做的事很多;如果不用await,處理同樣的邏輯,需要多寫很多代碼,並導致邏輯不清晰。

Task的分類

   非同步分為兩類 compute-base 和 IO-base。compute-base就是計算密集型,函數所有的操作都是在記憶體中,不涉及IO;如果運行這個函數,則單個線程利用率達100%;IO-base就是涉及到IO,IO包括文件讀寫,socket讀寫;這類非同步操作底層涉及到IOCP(完成埠)。相應的,Task也分為兩類。

  對於這兩個區別可以舉個例子來區分:一臺電腦為4個線程。如果同時有4個compute-base線程運行,cpu的利用率為100%。如果同時有4個 IO-base的非同步操作,cpu利用率可能遠遠低於100%。

  對於.net 庫,有些函數會有兩個版本:一個是同步操作,一個是非同步操作(函數名以Async結尾,返回值為Task)。舉個例子:

     

    這是WebClient類獲取網址內容函數。你會問DownloadStringTaskAsync是compute-base  Task,還是 IO-base Task?我可以肯定的告訴你:只要是.net基本類庫提供的非同步函數基本都是IO-base Task(微軟官方文檔是這樣要求)。其實這樣要求是有道理的:對於compute-base非同步,比較容易封裝;再者,這樣的非同步是不能大規模的併發的。如果16個線程cpu,同時併發16個這樣的非同步操作就是上限了;如果再多,反而會有害!

  有人說,如果基本類庫不提供 IO-base Task函數,我也可以封裝一下,這個也不難啊!代碼如下:

//把一個同步操作,改造成非同步
public static async Task<byte[]> DownloadDataAsync(string url)
{
            WebRequest request = WebRequest.Create(url);

            return await Task.Run(() =>
            {
                using (var response = request.GetResponse())
                using (var responseStream = response.GetResponseStream())
                using (var result = new MemoryStream())
                {
                    responseStream.CopyTo(result);
                    return result.ToArray();
                }
            });
 }

  上面函數如果說是非同步操作,也不錯。但是,這不是“好”的非同步操作!這是非同步操作中夾雜著同步IO。會導致線程等待。如果有100個這樣的非同步操作,就需要100個線程,這些線程大部分並沒在幹活,而是在等待! 對於“好”的非同步IO,如果同時有100個操作,甚至幾萬個操作,使用的線程都是有限的,一般不超過cpu線程數。這是怎麼實現的?這涉及到IOCP,說起來有些複雜,可以參考IOCP相關資料。類庫提供非同步IO操作,都是涉及到IOCP的。所以得到如下結論: 如果類庫不提供IO非同步函數,無論怎麼改造,不可能改造成“好”的非同步函數!

Task實現的基本原理

  Task變數狀態如下

  狀態簡要分為生成、執行、執行完畢這三個階段。如果執行完畢前獲取執行後的值Task.Result,函數就會阻塞。那我怎麼知道什麼時候完成,而又不阻塞?有兩種辦法,輪詢和回調通知。Task.IsCompleted屬性會指示函數是否執行完畢。輪詢不是一個好的辦法,採用回調通知是上策!

  回調通知有個缺點:處理邏輯不直觀,回調函數與非同步調用函數不在一塊,還有可能隔著很多行代碼或不在同一個文件。如果這樣的回調函數太多,對理解代碼邏輯造成困難,代碼不易維護。微軟也考慮到了這個問題,那就用await關鍵詞來解決。await幫你處理了回調函數的弊端,其實await後面的代碼與await前面的代碼不屬於同一個函數!await後面的代碼就是回調函數!微軟確實給我們解決了這個問題,但是又帶來另一個問題。好多人不明白,明明是同一個函數,怎麼實現了等待而又不阻塞當前線程!歸根到底,還是要理解await背後幫你幹了啥,否則就會一直困惑。

  要生成Task變數,只要理解幾個關鍵的處理步驟就行了。TaskCompletionSource類會幫助我們生成Task。如果IO完成,設置Task的狀態為完成就行了。後面,就會執行回調函數(await關鍵詞幫我幹了,你看不到回調)!

如何寫一個IO-base Task函數?

  大部分情況下不需要自己寫這樣的函數。但是,人是有好奇心的,如果不明白函數實現的原理,總是感覺不能釋懷!再者,明白函數實現原理,就能更好的利用這類函數。下麵講解一下如何利用IOCP來實現非同步函數。我沒有參考.net的源碼,只是根據邏輯推理應該這實現。肯定和.net源碼實現有出入,我寫這些代碼主要為了闡明Task實現原理。

IOCP處理邏輯

  對於IOCP,這裡不展開來講了,否則就跑題了。以socket讀取為例子,簡單總結一下:如果你要接收100個位元組的數據,你告訴IOCP你要接收100個位元組數據,並提供100個位元組的buffer,函數立即返回;數據到達後,IOCP通知你,數據到了,數據就存在你提供的buffer里。

   實現非同步IO偽代碼如下:

 class AyncInside
    {
        //完成埠句柄
        IntPtr iocpHandle = IntPtr.Zero;

        Task<byte[]> ReadFromSocket(int count)
        {
            //生成此次操作需要相關數據 
            TaskCompletionSourceRead readInfo = new TaskCompletionSourceRead();
            readInfo.Buffer = new byte[count];

            //如果沒生成iocp則生成。
            if (iocpHandle == IntPtr.Zero)
            {
                iocpHandle = CreateIocp();
            }

            // 告訴iocp,要讀取count位元組數據。函數不會阻塞,會立即返回
            //從完成埠收到數據後,會調用ReadScoketCallback
            //我們把readInfo也傳給函數。當回調時,該變數會傳給回調函數。
            ReadFromIocp(iocpHandle, readInfo.Buffer, readInfo, ReadScoketCallback);
            
            return readInfo.Tcs.Task;
        }


        void ReadScoketCallback(byte[] buffer, int readCount,object tag)
        {
            //tag就是調用ReadFromIocp時,傳的readInfo
            //便於我們知道非同步調用時的上下文數據。
            TaskCompletionSourceRead readInfo = tag as TaskCompletionSourceRead;
           
            if(buffer.Length == readCount )
            {
                //調用完SetResult後,await後面的代碼就會被執行!
                readInfo.Tcs.SetResult(buffer);
            }
            else if (buffer.Length > 0)
            {
                Array.Resize(ref buffer, readCount);
                readInfo.Tcs.SetResult(buffer);
            }
            else
            {
                readInfo.Tcs.TrySetException(new Exception("讀取數據異常!socket可能已斷開!"));
            }
        }

        private void ReadFromIocp(IntPtr iocpHandle, byte[] buffer, object tag,
            Action<byte[] , int,object> readScoketCallback)
        {
            throw new NotImplementedException();
        }

        private IntPtr CreateIocp()
        {
            throw new NotImplementedException();
        }

    }

    //封裝非同步讀取需要的數據
    class TaskCompletionSourceRead
    {
        public TaskCompletionSource<byte[]> Tcs { get; set; }
        public byte[] Buffer { get; set; }
    }

  上述代碼與實際可使用代碼差距還很大,我在這裡主要為了闡明原理。通過上面的代碼,我們可以看到,這個非同步函數並沒生成新的線程;網卡驅動和IOCP配合,幫我們接收了數據。所以這種方式才是真正可擴展的非同步IO。

後記 非同步IO和可擴展服務緊密關聯。對於.net core平臺,你會看到很多函數都是非同步的。理解和用好非同步IO函數非常重要。本文通過自己對非同步IO的理解,試圖通過代碼闡明非同步IO實現原理。希望你看過此文後,能對此有更深的理解!如果此文對你有所裨益,希望您給點個贊!


您的分享是我們最大的動力!

更多相關文章
  • 作為非專業的python選手,或者非專業的爬蟲選手,即使我們有一些編程基礎,有時想通過代碼從網上獲取一些信息,也不能徒手就能做,需要借鑒一些成熟的方案、代碼。 ...
  • 前言 亂碼是我們在程式開發中經常碰到且讓人頭疼的一件事,尤其是我們在做javaweb開發,如果我們沒有清楚亂碼產生的原理,碰到亂碼問題了就容易摸不著頭腦,無從下手。 亂碼主要出現在兩部分,如下: 第一,瀏覽器通過表單提交到後臺,如果表單內容有中文,那麼後臺收到的數據可能會出現亂碼。 第二,後端伺服器 ...
  • 資源訪問介面 由於JDK提供的資源訪問類並不能很好的滿足底層資源的訪問需求,所以Spring設計了一個Resource介面。Spring框架使用Resource裝載各種資源,包括配置文件資源、國際化屬性文件資源等 Resource具體的實現類圖 Resource介面的主要方法 1. boolean ...
  • "CodeForces Gym題目頁面傳送門" 有$1$個$n1\times m1$的字元矩陣$a$和$1$個$n2\times m2$的字元矩陣$b$,求$a,b$的最大公共子矩陣。輸出這個最大公共子矩陣的行數、列數和左上角分別在$a,b$中的坐標。若無解,輸出$``\text{0 0''}$。若 ...
  • 1,分頁嘛先要有個SQL 程式才能寫下去 先提供下SQL的思路,對於分頁的SQL我之前帖子有介紹,就不一一介紹了 select top pageSize * --顯示數量 from (select row_number() over(order by EG_ID asc) as rownumber, ...
  • 昨天看新聞,說人教社開放了人教版中小學教材電子版的春季教材(下載地址:http://bp.pep.com.cn/jc/ ),就想著給兒子全下載下來以備後用。不過人工下載真是麻煩枯燥,就為了省事,就寫個爬蟲。原本打算用python,回頭想了下,好久沒用C#了,就用C#寫吧。 具體思路和實現步驟如下 1 ...
  • 本例通過Timer的tick()方法觸發TimerCallback委托來開闢新的線程,線程中的具體工作通過一個靜態方法作為參數給TimerCallback委托。 using System; using System.Threading; /* 這是一個關於 timer開啟多線程的一個例子 * 1.T ...
  • 天天宅在家裡,沒什麼事做,錄個教學視頻吧! 發到了視頻網站上去根本沒人看,傷心ing啊! 不知cnblogs上面是否讓我發! https://www.bilibili.com/video/av88668329 加qq群 336090194可以下載源碼! ...
一周排行
  • 微信公眾號dotnet跨平臺2020年初做的一個關於中國.NET開發者調查收到了開發者近 1400 條回覆。這份調查報告涵蓋了開發者工具鏈的所有部分,包括編程語言、應用架構、應用伺服器、運行時平臺、框架技術、框架配置、IDE、.NET/.NET Core 發行版部署模式、構建工具和Kubernete... ...
  • Winform控制項的雙緩衝。控制項的雙緩衝屬性是隱藏的,可以通過反射改變其屬性值。 lv.GetType().GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(lv, true, ...
  • 1. 需求 上圖這種包含多選(CheckBox)和單選(RadioButton)的菜單十分常見,可是在WPF中只提供了多選的MenuItem。順便一提,要使MenuItem可以多選,只需要將MenuItem的 屬性設置為True: 不知出於何種考慮,WPF沒有為MenuItem提供單選的功能。為了在 ...
  • gRPC的結構 在我們搭建gRPC通信系統之前,首先需要知道gRPC的結構組成。 首先,需要一個server(伺服器),它用來接收和處理請求,然後返迴響應。 既然有server,那麼肯定有client(客戶端),client的作用就是向server發送請求,具體就是生成一個請求,然後把它發送到ser ...
  • 區別 OpenId: Authentication :認證 Oauth: Aurhorize :授權 輸入賬號密碼,QQ確認輸入了正確的賬號密碼可以登錄 認證 下麵需要勾選的覆選框(獲取昵稱、頭像、性別) 授權 OpenID 當你需要訪問A網站的時候,A網站要求你輸入你的OpenId,即可跳轉到你的 ...
  • 前言 預計是通過三篇來將清楚asp.net core 3.x中的授權:1、基本概念介紹;2、asp.net core 3.x中授權的預設流程;3、擴展。 在完全沒有概念的情況下無論是看官方文檔還是源碼都暈乎乎的,希望本文能幫到你。不過我也是看源碼結合官方文檔看的,可能有些地方理解不對,所以只作為參考 ...
  • 簡介 基於生產者消費者模式,我們可以開發出線程安全的非同步消息隊列。 知識儲備 什麼是生產者消費者模式? 為了方便理解,我們暫時將它理解為垃圾的產生到結束的過程。 簡單來說,多住戶產生垃圾(生產者)將垃圾投遞到全小區唯一一個垃圾桶(單隊列),環衛將垃圾桶中的垃圾進行處理(消費者)。就是一個生產者消費者 ...
  • 很多時候,需要對類中的方法進行一些測試,來判斷是否能按要求輸出預期的結果。 C#提供了快速創建單元測試的方法,但單元測試不僅速度慢不方便,大量的單元測試還會拖慢項目的啟動速度。 所以決定自己搞個方便的測試用例。 控制台一句話調用。 測試用例.註冊並Print(EnumEx.Name); 結果畫面: ...
  • 常成員函數不能改變數據成員的值,例如定義坐標類Coordinate,成員函數changeX():void Coordinate::changeX(){ x = 10;}雖然changeX()沒有參數,但是它隱含一個參數——this指針:void Coordinate::changeX(Coordin... ...
  • 因為新冠肺炎疫情,診所還沒復工。這是在家用手機敲的,代碼顯示有問題。等復工以後在電腦上改,各位先湊和看吧。 支持向量機(Support Vector Machine, SVM)是一種基於統計學習的模式識別的分類方法,主要用於模式識別。所謂支持向量指的是在分割區域邊緣的訓練樣本點,機是指演算法。就是要找 ...
x