探究SynchronizationContext在.Net异步编程中的地位
原文:探究SynchronizationContext在.Net异步编程中的地位
引言:
多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.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();
}
}

不要混合使用异步、同步代码,始终使用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可获取当前线程的同步上下文。利用该对象,可在线程池线程B执行完成后,尝试切换到原调用线程A执行特定代码。
2. await/async语法糖与SynchronizationContext 的关系?

② 应用await时,框架捕获当前环境, 存储在SynchronizationContext 对象并附加于以上Task;
③ 同时,控制权返回到原上层调用函数,返回一个未完成的Task<int>对象,这个时候需要关注上层调用函数使用 await异步等待还是使用Result/Wait()方式同步等待
④ 异步任务T执行完成,await之后的代码将会成为continuation block, 默认情况下利用捕获的SynchronizationContext 对象执行该continuation block 代码。
内部实际是将continuation block代码放入 SynchronizationContext 的Post方法。
不同的.NET框架因各自独特的需求 有不同的 Post实现(Post是一个虚方法):
- 默认的SynchronizationContext.Post实现是将 委托通过QueueUserWorkItem传递给 ThreadPool。
- Windows Form有WindowsFormSynchronizationContext, Post方法将委托传递给 Control.BeginInvoke
- WPF 有DispatcherSynchronizationContext , Post方法将委托传递给 Dispatcher.BeginInvoke
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在线程切换后帮我们有能力执行各种骚操作。
感谢您的认真阅读,如有问题请大胆斧正,如果您觉得本文对你有用,不妨右下角点个或加关注。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置注明本文的作者及原文链接,否则保留追究法律责任的权利。
探究SynchronizationContext在.Net异步编程中的地位的更多相关文章
- 【转】C# Async/Await 异步编程中的最佳做法
Async/Await 异步编程中的最佳做法 Stephen Cleary 近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支 ...
- C#异步编程中的最佳实践(做法)
原文地址Stephen Cleary 写得很详细,尤其讲到了 GUI 上下文调用,在APS.NET中它会阻塞 GUI 线程,从而导致死锁.而控制台中却不存在这个问题. 比如开发过程中本地写控制台程序测 ...
- 你不知道的this—JS异步编程中的this
Javascript小学生都知道了javascript中的函数调用时会 隐性的接收两个附加的参数:this和arguments.参数this在javascript编程中占据中非常重要的地位,它的值取决 ...
- 一种C# TCP异步编程中遇到的问题
最近在维护公司的一个socket服务端工具,该工具主要是提供两个socket server服务,对两端连接的程序进行数据的透明转发. 程序运行期间,遇到一个问题,程序的一端是GPRS设备,众所周知,G ...
- 异步编程中使用帮助类来实现Thread.Start()的示例
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 异步编程中的最佳做法(async await)
阅读1:http://blog.csdn.net/nacl025/article/details/9163495 阅读2:http://www.cnblogs.com/x-xk/archive/201 ...
- 【.NET异步编程系列2】掌控SynchronizationContext避免deadlock
引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式. 相信很多开发者都看到如下异步编程实践原 ...
- 【异步编程】Part2:掌控SynchronizationContext避免deadlock
引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式. 相信很多开发者都看到如下异步编程实践原 ...
- 抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext
长话短说,本文带大家抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext 引言 C#异步编程语法糖async/await,使开发者很容易就能编写异步代码. ...
随机推荐
- python模块详解 random os
random模块 常用方法 random.random() 随机产生一个小于1的浮点数 import random print(random.random()) #0.4153761818276826 ...
- Azure 进阶攻略 | 上云后的系统,「门禁」制度又该如何实现?
各位办公室白领们,不妨回想一下自己每天去公司上班时的一些细节. 为避免「闲杂人等」进入工作场所,我们需要证明自己是这家公司的员工才能进入,对吧!所有员工,无论所属部门或职位,都必须先证明自己身份,例如 ...
- System Center Configuration Manager 2016 配置安装篇(Part1)
SCCM 2016 配置管理系列(Part 1- 4) 介绍AD01上配置了Active Directory域服务(ADDS),然后将Configuration Manager服务器(CM16)加入到 ...
- JAX-WS @WebParam自定义参数名称无效
在使用myeclipse 自动对service方法类进行创建webservice服务时,默认创建参数命名都是arg0-9 这样就导致生成的xml配置文件命名不规范,需要对参数名称进行修改: myecl ...
- OpenGL纹理高级
矩形纹理 对于二维纹理来说,除了GL_TEXTURE_2D之外,使用GL_TEXTURE_RECTANGLE就可以使用矩形纹理. 矩形纹理几大特点: 不能Mip,只能加载glTexImage2D的le ...
- java常用输出技巧,debug
package control; import javax.swing.JFrame; public class DebugTest { public static void main(String[ ...
- spring boot学习1
转:https://blog.csdn.net/jsyxcjw/article/details/46763639 1 开始 1.1 spring介绍 Spring Boot使开发独立的,产品级别的基于 ...
- 创建 XXXXXXXX 的配置节处理程序时出错: 请求失败
今天碰到这个错误,之前的程序在测试的时候都没有问题,同样的程序打包通过QQ传给其他人,在XP下测试也没有问题,我的Win7系统从QQ信箱下载压缩包,解压之后执行程序就会出问题,本来还是考虑自己程序是不 ...
- Matlab将矩阵保存为图像
imwrite(image,'image.jpg'); image为矩阵的内容 image.jpg为要保存的图像的名字
- ajax(form)图片上传(spring)
第一步:spring-web.xml <!--配置上传下载--> <bean id="multipartResolver" class="org.spr ...