1. 程式人生 > >C#執行非同步操作的幾種方式比較和總結

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

原文地址:http://www.cnblogs.com/durow/p/4826653.html

轉載此文的目的就是想讓自己記住曾經尋找過這些資料

感謝這位博主的無私奉獻

0x00 引言

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

0x01 測試方法

IDE:VS2015 Community

.NET版本:4.5

使用函式隨機休眠100到500毫秒來模擬耗時任務,並返回任務花費的時間,在UI執行緒上呼叫這個方法會造成阻塞,導致UI假死,因此需要通過非同步方式執行這個任務,並在資訊輸出區域顯示花費的時間。

 

主介面中通過各種不同按鈕測試不同型別的非同步操作

 

0x02 使用Thread進行非同步操作

使用ThreadPool進行非同步操作的方法如下所示,需要注意的就是IsBackground預設為false,也就是該執行緒對呼叫它的執行緒不產生依賴,當呼叫執行緒退出時該執行緒也不會結束。因此需要將IsBackground設定為true以指明該執行緒是後臺執行緒,這樣當主執行緒退出時該執行緒也會結束。另外跨執行緒操作UI還是要藉助Dispatcher.BeginInvoke(),如果需要阻塞UI執行緒可以使用Dispatcher.Invoke()。

 

0x03 使用ThreadPool進行非同步操作

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

 

0x04 使用Task進行非同步操作

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

 

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

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

 

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

 

async/await也是從執行緒池中取執行緒,可實現執行緒複用,而且程式碼簡潔容易閱讀,非同步操作返回後會自動返回呼叫執行緒,是執行非同步操作的首選方式。而且雖然是C#5的新特性,但C#4可以通過下載升級包來支援async/await。

0x06 關於效率

以上嘗試的方法除了直接使用Thread之外,其他幾種都是直接或間接使用執行緒池來獲取執行緒的。從理論上來分析,建立執行緒時要給執行緒分配棧空間,執行緒銷燬時需要回收記憶體,建立執行緒也會增加CPU的工作。因此可以連續建立執行緒並記錄消耗的時間來測試效能。測試程式碼如下所示:

 

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

 

測試結果:

 

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

 

0x07 結論

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

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

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

0x08 相關下載

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