關於異步的初步認識

  在當初試用多線程的時候發現多線程能減輕或消除大量繁雜操做或過長等待時間形成的停滯感(就是線程阻塞)。後來發現使用異步操做也能達到相同的效果。可是二者之間是有區別的,以前在知識庫裏看了一些文章,我也記錄了一下(有人云亦云的感受),順便也擺出一些我的觀點。數據庫

  多線程和異步雖然均可以減輕或消除線程阻塞而形成的停滯感,可是二者的本質上是有區別的網絡

  多線程是軟件級別上的機制,在微觀上它是分配CPU的時間片給某個進程中的各條線程,得到時間片的線程就能夠處理它的任務,也就是執行代碼。在其中負責調度CPU資源的就是操做系統,因此多線程是否能實現取決於操做系統,現今絕大部分操做系統都是多線程的系統,在DOS下是不支持多線程的。多線程

  異步則是硬件級別上的機制,在大學學習《計算機組成原理》時,就提過硬件的DMA(Direct Memory Access,直接內存存取),它是讓一些計算機的外部設備(網卡,磁盤等)在不用消耗CPU時間的狀況下,直接與內存交互進行數據讀寫,在此期間CPU能夠着手其餘事情,在IO完畢後才把調度權還給CPU。因而可知,過中不須要操做系統的支持,因此按照這樣的思路去想,只須要計算機的硬件支持的話,在DOS中也能實現異步操做(這個在網上看到的,實際上我也沒搞個DOS去實踐)。異步

  由上述的區別能夠推斷出他們的適用場合,異步是硬件層面的,一些關於硬件層面上的操做用起異步來會適合一些,例如文件的讀寫操做,數據庫訪問,網絡訪問等;多線程是軟件層面的,CPU是否把時間片分配給當前線程,與可否讓外部設備直接訪問內存關係不大,相同的操做也是一樣要消耗相同的CPU時間,相比起來,一些要CPU花費大量時間去處理的操做用多線程去實現會恰當一點。學習

  理論上就如前面所說的,可是回到.NET Framework裏面,貌似是另外一回事了。經過運行如下代碼測試

 1         static void Main(string[] args)
 2         {
 3             PrintThreadInfo("Main");
 4             Action act = delegate() { PrintThreadInfo("Action"); };
 5             IAsyncResult ir = act.BeginInvoke(new AsyncCallback(AsyncCallback), act);
 6             
 7             Console.ReadLine();
 8         }
 9 
10         static void AsyncCallback(IAsyncResult result)
11         {
12             PrintThreadInfo("Callback");
13             Action caller = result.AsyncState as Action;
14             caller.EndInvoke(result);
15         }
16 
17         static void PrintThreadInfo( string host )
18         {
19             Console.WriteLine(string.Format( " {2} call: Current Thread Id is {0}, it {1} in threadpool",Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread?"is":"isn't",host ));
20         }

 

 

  發現異步操做實際上仍是利用了多線程,並且這條新開闢的線程是來源於線程池ThreadPool的。在MSDN上翻找了一下BeginInvoke的解釋的確是異步調用的。它確確實實屬於APM模式(BeginXXX/EndXXX)。那個再觀察一下別的類在APM模式下的工做狀況spa

 1         static void Main(string[] args)
 2         {
 3             PrintThreadInfo("Main");
 4             byte[] datas = Encoding.ASCII.GetBytes("hello world");
 5             FileStream fs = new FileStream("abc.txt", FileMode.Create, FileAccess.Write,FileShare.Write, 1024, FileOptions.Asynchronous);
 6             fs.BeginWrite(datas, 0, datas.Length, new System.AsyncCallback(AsyncCallback), fs);
 7 
 8 
 9             Console.ReadLine();
10         }
11 
12         static void AsyncCallback(IAsyncResult result)
13         {
14             PrintThreadInfo("Callback");
15             FileStream caller = result.AsyncState as FileStream;
16             caller.EndWrite(result);
17             caller.Close();
18             caller.Dispose();
19         }

  這裏選取了FileStream做例子,但從結果能夠看出,縱使確確實實是異步操做,確確實實是文件寫入,可是仍然是有調用了線程池,使用了線程。看回第一個例子的結果BeginInvoke和回調方法都是在同一條線程上執行的,相比起第二個例子就有個侷限性,在BeginWrite調用的時候沒辦法看查看是否有使用線程去進行寫操做,第二行信息是在回調時顯示出來的。那這裏是否和上一個例子同樣二者都在同一個線程上運行呢?我有個比較拙劣的辦法以下圖所示操作系統

  第一個例子中的狀況線程

第二個例子中的狀況code

  雖然這樣斷點測試貌似有點偏差,不知有否說服力。對比之下仍是能夠看出第一個例子它調用異步的時候就建立了線程,嚴格意義上並不屬於異步,第二個例子調用異步方法時沒有建立線程,直到回傳的時候纔去建立了線程。能夠初步證明在調用BeginWrite的時候並無去建立線程,確實是使用了DMA機制,確確實實是異步調用了。

  參考趙劼老師說的話,CLR會(經過Windows API)發出一個IRP(I/O Request Packet)。當設備準備穩當,就會找出一個它「最想處理」的IRP(例如一個讀取離當前磁頭最近的數據的請求)並進行處理,處理完畢後設備將會(經過Windows)交還一個表示工做完成的IRP。CLR會爲每一個進程建立一個IOCP(I/O Completion Port)並和Windows操做系統一塊兒維護。IOCP中一旦被放入表示完成的IRP以後(經過內部的ThreadPool.BindHandle完成),CLR就會盡快分配一個可用的線程用於繼續接下去的任務。

  我的理解就是CLR與底層硬件交互,讓相應設備的再也不消耗CPU去設備訪存。正如文章開頭的理論部分所言,異步屬於硬件方面的,因此一些委託的異步調用BeginInvoke並是假異步,例如上面文件操做的異步纔是真異步,能實現真異步的有如下方法

  • FileStream操做:BeginRead、BeginWrite(只有構造FileStream時傳入FileOptions.Asynchronous參數才能獲取真正的異步操做,不然仍然是假異步)。
  • DNS操做:BeginGetHostByName、BeginResolve。
  • Socket操做:BeginAccept、BeginConnect、BeginReceive等等。
  • WebRequest操做:BeginGetRequestStream、BeginGetResponse。
  • SqlCommand操做:BeginExecuteReader、BeginExecuteNonQuery等等(要在鏈接字符串中把Asynchronous Processing設爲true,不然調用異步方法時會拋異常)。
  • WebServcie調用操做:例如.NET 2.0或WCF生成的Web Service Proxy中的BeginXXX方法、WCF中ClientBase<TChannel>的InvokeAsync方法。

  最後要記錄一下的就是使用APM模式的時候必定要調用回調方法或者EndXXX,不然線程資源沒法回收,有可能致使系統崩潰。

  以上文章有參考趙劼老師的《正確使用異步操做》,還有一部分是在下的拙見,各位以爲在下有什麼說錯的歡迎批評指正,有什麼建議或意見儘管說說。謝謝!

相關文章
相關標籤/搜索