多線程編程/異步編程很是複雜,有不少概念和工具須要去學習,貼心的.NET提供Task線程包裝類和await/async異步編程語法糖簡化了異步編程方式。編程
相信不少開發者都看到以下異步編程實踐原則:json
實踐原則 | 說明 | 例外狀況 | |
① | 避免 Async Void | 最好使用 async Task 方法而不是 async void 方法 | 事件處理程序 |
② | 始終使用 await | 不要混合阻塞式代碼和異步代碼 | 控制檯 main 方法 |
③ | 配置上下文 | 儘量使用ConfigureAwait(false) | 須要上下文的方法 |
UI 例子:點擊按鈕觸發了一個遠程HTTP請求,用請求的返回值修改UI控件, 如下代碼會引起deadlock (相似狀態出如今Windows Form、WPF)多線程
public static async Task<JObject> GetJsonAsync(Uri uri) { using (var client = new HttpClient()) { var jsonString = await client.GetStringAsync(uri); return JObject.Parse(jsonString); } } // 頂層調用方法 public void Button1_Click(...) { var jsonTask = GetJsonAsync(...); textBox1.Text = jsonTask.Result; }
public static async Task<JObject> GetJsonAsync(Uri uri) { using (var client = new HttpClient()) { var jsonString = await client.GetStringAsync(uri); return JObject.Parse(jsonString); } } // My "top-level" method. public class MyController : ApiController { public string Get() { var jsonTask = GetJsonAsync(...); return jsonTask.Result.ToString(); } }
不要混合使用異步、同步代碼,始終使用async/await語法糖編寫異步代碼框架
在等待的異步任務內應用ConfigureAwait(false)方法 (:再也不嘗試從捕獲的同步上下文執行異步編程的後續代碼)異步
爲何要有SynchronizationContext 對象async
闡述await關鍵字與SynchronizationContext對象交互原理異步編程
以上代碼爲何會有deadlock, 另外ASP.NET Core爲何不會發生以上死鎖函數
提供在各類同步模型中傳播同步上下文的基本功能。此類實現的同步模型的目的是容許公共語言運行庫的內部異步/同步操做使用不一樣的同步模型正常運行。
上面的定義給個人印象是:在線程切換過程當中保存前置線程執行的上下文環境。工具
咱們你們都知道:Windows Form和WPF都基於相似的原則: 不容許在非UI線程上操做 UI元素學習
這個時候咱們能夠捕獲當前執行環境SynchronizationContext,利用這個對象切換回原UI線程。
public static void DoWork() { //On UI thread var sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem(delegate { // do work on ThreadPool sc.Post(delegate { // do work on the original context (UI) }, null); }); }
SynchronizationContext表示代碼正在運行的當前環境,每一個線程都有本身的SynchronizationContext,經過SynchronizationContext.Current可獲取當前線程的同步上下文。在異步線程切換場景中,咱們並不須要代碼在哪一個線程上啓動,只須要使用SynchronizationContext ,就能夠返回到啓動線程。
不一樣的.NET框架因各自獨特的需求有不一樣SynchronizationContext子類(一般是重寫Post虛方法):
- 默認SynchronizationContext封裝的是線程池內線程,將執行委託發送到線程池中任意線程。
- asp.Net有AspNetSynchronizationContext,在一個異步page處理過程當中,context始終使用的是線程池中某個特定線程
- Windows Form有WindowsFormSynchronizationContext,封裝單個UI線程,Post方法將委託傳遞給 Control.BeginInvoke
- WPF 有DispatcherSynchronizationContext, 瞭解到與WinForm 相似。
② 應用await時,框架捕獲當前環境, 存儲在SynchronizationContext 對象並附加於以上Task;
③ 同時,控制權返回到原上層調用函數,返回一個未完成的Task<int>對象,這個時候須要關注上層調用函數使用 await異步等待仍是使用Result/Wait()方式同步等待
④ 異步任務T執行完成,await以後的代碼將會成爲continuation block, 默認狀況下利用捕獲的SynchronizationContext 對象執行該continuation block 代碼。
內部實際是將continuation block代碼放入SynchronizationContext 的Post方法。
仔細觀察引言代碼,控制返回到 上層調用函數時, 該調用函數使用Result屬性去等待任務結果,Result/Wait()等同步方式會致使調用線程掛起等待任務完成。而在異步方法內部,await觸發的異步任務執行完成後,會嘗試利用捕獲的同步上下文執行剩餘代碼,而該同步上下文中的線程正同步等待整個異步任務完成,造成死鎖。
正由於如此,咱們提出:
- 在原調用函數始終 使用 await方法,這樣該線程是異步等待 任務完成。
- 在異步任務內部應用ConfigureAwait(false)方法, 不嘗試使用捕獲的同步上下文執行後繼代碼
MSDN ConfigureAwait(): true to attempt to marshal the continuation back to the original context captured; otherwise, false
另外注意:ASP.NET Core,,控制檯程序不存在SynchronizationContext , 故不會發生相似的死鎖。
雖然await/async 語法糖讓咱們在編寫.NET 異步程序時駕輕就熟、爲所欲爲,可是不要忘記了SynchronizationContext 在其中轉承起合的做用。
利用可以保存當前執行代碼的上下文特性,SynchronizationContext在線程切換後幫咱們有能力執行各類騷操做。
感謝您的認真閱讀,若有問題請大膽斧正,若是您以爲本文對你有用,不妨右下角點個或加關注。
本文版權歸做者全部,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置註明本文的做者及原文連接,不然保留追究法律責任的權利。