天天看點

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

C#執行異步操作的幾種方式比較和總結

0x00 引言

之前寫程式的時候在遇到一些比較花時間的操作例如HTTP請求時,總是會new一個Thread處理。對XxxxxAsync()之類的方法也沒去了解過,倒也沒遇到什麼大問題。最近因為需求要求用DevExpress寫界面,跑起來後發現比Native控件效率差好多。這才想到之前看到的“金科玉律”:不要在UI線程上執行界面無關的操作,是以集中看了下C#的異步操作,分享一下自己的比較和總結。

0x01 測試方法

IDE:VS2015 Community

.NET版本:4.5

使用函數随機休眠100到500毫秒來模拟耗時任務,并傳回任務花費的時間,在UI線程上調用這個方法會造成阻塞,導緻UI假死,是以需要通過異步方式執行這個任務,并在資訊輸出區域顯示花費的時間。

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

主界面中通過各種不同按鈕測試不同類型的異步操作

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

0x02 使用Thread進行異步操作

使用ThreadPool進行異步操作的方法如下所示,需要注意的就是IsBackground預設為false,也就是該線程對調用它的線程不産生依賴,當調用線程退出時該線程也不會結束。是以需要将IsBackground設定為true以指明該線程是背景線程,這樣當主線程退出時該線程也會結束。另外跨線程操作UI還是要借助Dispatcher.BeginInvoke(),如果需要阻塞UI線程可以使用Dispatcher.Invoke()。

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

0x03 使用ThreadPool進行異步操作

ThreadPool(線程池)的出現主要就是為了提高線程的複用(類似的還有通路資料庫的連接配接池)。線程的建立是開銷比較大的行為,為了達到較好的互動體驗,開發中可能會大量使用異步操作,特别是需要頻繁進行大量的短時間的異步操作時,頻繁建立和銷毀線程會在造成很多資源的浪費。而通過線上程池中存放一些線程,當需要建立線程執行操作時就從線程池中取出一個已經存在的空閑線程使用,如果此時沒有空閑線程,且線程池中的線程數未達到線程池上限,則建立一個線程,使用完成後再放回到線程池中。這樣可以極大程度上省去線程建立的開銷。線程池中線程的最小和最大數都可以指定,不過多數情況下無需指定,CLR有一套管理線程池的政策。ThreadPool的使用非常簡單,代碼如下所示。跨線程操作UI仍需借助Dispatcher。

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

0x04 使用Task進行異步操作

Task進行異步操作時也是從線程池中擷取線程進行操作,不過支援的操作更加豐富一些。而且Task<T>可以支援傳回值,通過Task的ContinueWith()可以在Task執行結束後将傳回值傳入以進行操作,但在ContinueWith中跨線程操作UI仍需借助Dispatcher。另外Task也可以直接使用靜态方法Task.Run<T>()執行異步操作。

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

0x05 使用async/await進行異步操作

這個是C#5中的新特性,當遇到await時,會從線程池中取出一個線程異步執行await等待的操作,然後方法立即傳回。等異步操作結束後回到await所在的地方接着往後執行。await需要等待async Task<T>類型的函數。詳細的使用方法可參考相關資料,測試代碼如下所示。異步結束後的會傳回到調用線程,是以修改UI不需要Dispatcher。

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

也可以把TestTask包裝成async方法,這樣就可以使用上圖中注釋掉的兩行代碼進行處理。包裝後的異步方法如下所示:

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

async/await也是從線程池中取線程,可實作線程複用,而且代碼簡潔容易閱讀,異步操作傳回後會自動傳回調用線程,是執行異步操作的首選方式。而且雖然是C#5的新特性,但C#4可以通過下載下傳更新包來支援async/await。

0x06 關于效率

以上嘗試的方法除了直接使用Thread之外,其他幾種都是直接或間接使用線程池來擷取線程的。從理論上來分析,建立線程時要給線程配置設定棧空間,線程銷毀時需要回收記憶體,建立線程也會增加CPU的工作。是以可以連續建立線程并記錄消耗的時間來測試性能。測試代碼如下所示:

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

當測試Thread時每次測試在連續建立線程時記憶體和CPU都會有個小突起,不過線上程結束後很快就會降下去,在我的電腦上連續建立100個線程大概花費120-130毫秒。如圖所示:

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

測試結果:

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

使用基于線程池的方法建立線程時,有時第一次會稍慢一些,應該是線程池内線程不足,時間開銷在0-15毫秒,第一次建立記憶體也會上升。後面再測試時時間開銷為0毫秒,記憶體表現也很平穩,CPU開銷分布比較平均。測試結果如圖所示:

C#執行異步操作的幾種方式比較和總結 C#執行異步操作的幾種方式比較和總結

0x07 結論

在執行異步操作時應使用基于線程池的操作,從代碼的簡潔程度和可讀性上優先使用async/await方式。對于較老的.NET版本可以使用Task或ThreadPool。符合以下情況的可以使用Thread:

1、線程建立後需要持續工作到主線程退出的。這種情況下就算使用線程池線程也不會歸還,實作不了複用,可以使用Thread。

2、線程在主線程退出後仍需要執行的,這種情況使用線程池線程無法滿足需求,需要使用Thread并制定IsBackground為false(預設)。

0x08 相關下載下傳

測試程式代碼在:https://github.com/durow/TestArea/tree/master/AsyncTest/AsyncTest

附轉載位址:http://www.cnblogs.com/durow/p/4826653.html