多线程编程/异步编程很是复杂,有不少概念和工具须要去学习,贴心的.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在线程切换后帮咱们有能力执行各类骚操做。
感谢您的认真阅读,若有问题请大胆斧正,若是您以为本文对你有用,不妨右下角点个或加关注。
本文版权归做者全部,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置注明本文的做者及原文连接,不然保留追究法律责任的权利。