【异步编程】Part2:掌控SynchronizationContext避免deadlock
引言:
多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式。
相信很多开发者都看到如下异步编程实践原则:
| 实践原则 | 说明 | 例外情况 | |
| ① | 避免 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();
}
}
解决以上deadlock需利用以上第②③条编程原则:不要混合使用异步、同步代码,始终使用async/await语法糖编写异步代码
在等待的异步任务内应用ConfigureAwait(false)方法 (:不再尝试从捕获的同步上下文执行异步编程的后续代码)
为什么要有SynchronizationContext 对象
阐述await关键字与SynchronizationContext对象交互原理
以上代码为什么会有deadlock, 另外ASP.NET Core为什么不会发生以上死锁
1. The Need for SynchronizationContext
提供在各种同步模型中传播同步上下文的基本功能。此类实现的同步模型的目的是允许公共语言运行库的内部异步/同步操作使用不同的同步模型正常运行。
上面的定义给我的印象是:在线程切换过程中保存前置线程执行的上下文环境。
我们大家都知道: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 类似。
2. await/async语法糖与SynchronizationContext 的关系?

② 应用await时,框架捕获当前环境, 存储在SynchronizationContext 对象并附加于以上Task;
③ 同时,控制权返回到原上层调用函数,返回一个未完成的Task<int>对象,这个时候需要关注上层调用函数使用 await异步等待还是使用Result/Wait()方式同步等待
④ 异步任务T执行完成,await之后的代码将会成为continuation block, 默认情况下利用捕获的SynchronizationContext 对象执行该continuation block 代码。
内部实际是将continuation block代码放入SynchronizationContext 的Post方法。
3.引言代码为什么发生deadlock, 而ASP.NET Core/控制台程序为什么不会发生类似deadlock?
仔细观察引言代码,控制返回到 上层调用函数时, 该调用函数使用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在线程切换后帮我们有能力执行各种骚操作。
感谢您的认真阅读,如有问题请大胆斧正,如果您觉得本文对你有用,不妨右下角点个
或加关注。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置注明本文的作者及原文链接,否则保留追究法律责任的权利。
【异步编程】Part2:掌控SynchronizationContext避免deadlock的更多相关文章
- 【.NET异步编程系列2】掌控SynchronizationContext避免deadlock
引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式. 相信很多开发者都看到如下异步编程实践原 ...
- 【C# TAP 异步编程】四、SynchronizationContext 同步上下文|ExecutionContext
一.同步上下文(SynchronizationContext)概述 由来 多线程程序在.net框架出现之前就已经存在了.这些程序通常需要一个线程将一个工作单元传递给另一个线程.Windows程序以消息 ...
- 探究SynchronizationContext在.Net异步编程中的地位
原文:探究SynchronizationContext在.Net异步编程中的地位 引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/a ...
- 抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext
长话短说,本文带大家抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext 引言 C#异步编程语法糖async/await,使开发者很容易就能编写异步代码. ...
- Async 和 Await的性能(.NET4.5新异步编程模型)
异步编程长时间以来一直都是那些技能高超.喜欢挑战自我的开发人员涉足的领域 — 这些人愿意花费时间,充满热情并拥有心理承受能力,能够在非线性的控制流程中不断地琢磨回调,之后再回调. 随着 Microso ...
- 【温故而知新-万花筒】C# 异步编程 逆变 协变 委托 事件 事件参数 迭代 线程、多线程、线程池、后台线程
额基本脱离了2.0 3.5的时代了.在.net 4.0+ 时代.一切都是辣么简单! 参考文档: http://www.cnblogs.com/linzheng/archive/2012/04/11/2 ...
- 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法
什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...
- 异步编程 In .NET
概述 在之前写的一篇关于async和await的前世今生的文章之后,大家似乎在async和await提高网站处理能力方面还有一些疑问,博客园本身也做了不少的尝试.今天我们再来回答一下这个问题,同时我们 ...
- 【转】【C#】C# 5.0 新特性——Async和Await使异步编程更简单
一.引言 在之前的C#基础知识系列文章中只介绍了从C#1.0到C#4.0中主要的特性,然而.NET 4.5 的推出,对于C#又有了新特性的增加--就是C#5.0中async和await两个关键字,这两 ...
随机推荐
- Bitmaps
核心知识点: 1.Bitmaps是一种特殊的“数据结构”,实质上是一个字符串,操作单元是位. 2.命令: a.setbit:设置值,只能存储0和1,适用二元判断类型 b.getbit:获取值 c.bi ...
- web项目中从不同的路径读取文件
项目中的配置文件可以放在classpath下,webapp下获取其他任何一个指定的绝对地址,读取这些文件就从这三个地方去找.主要代码如下: private List<String> get ...
- map select reduce
map: 针对每个element进行变换并返回整个修改后的map a.map do |item| a.upcase end a.map(&:upcase) 一样的效果, &:代表了it ...
- Zookeeper四字命令
ZooKeeper 支持某些特定的四字命令(The Four Letter Words)与其进行交互.它们大多是查询命令,用来获取 ZooKeeper 服务的当前状态及相关信息.用户在客户端可以通过 ...
- 20145239杜文超 《Java程序设计》第2周学习总结
20145239 <Java程序设计>第2周学习总结 教材学习内容总结 第三章主要介绍了Java语言的基础语法. 要求我们认识类型与变量,学习运算符的基本使用以及了解类型转换.运用基本流程 ...
- 使用CL命令编译cpp文件
缘起,我的vs 2003无法新建工程,又不喜欢用vs 2013那样的重量级开发工具(就写两行代码,测试测试一些基本的语法规则或算法). 想来vs应该可以像GCC或G++那样直接用命令行编译Cpp文件, ...
- laravel基础课程---15、分页及验证码(lavarel分页效果如何实现)
laravel基础课程---15.分页及验证码(lavarel分页效果如何实现) 一.总结 一句话总结: 数据库的paginate方法:$data=\DB::table("user" ...
- redis持久化的方式RDB 和 AOF
redis持久化的方式RDB 和 AOF 一.对Redis持久化的探讨与理解 目前Redis持久化的方式有两种: RDB 和 AOF 首先,我们应该明确持久化的数据有什么用,答案是用于重启后的数据恢复 ...
- 重新拾取:ASP.NET Core WebApi 使用Swagger支持授权认证
园子里已经有很多.NET Core 集成Swagger的文章,但对于使用授权的介绍蛮少的. public static class SwaggerServiceExtensions { public ...
- CSDN不登陆看博文
做前端的朋友说,手动改太Low,给了段JS代码: javascript: void((function() {var divElement = document.getElementById('art ...