点击蓝字

关注我们

本文带大家抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext。

引言

C#异步编程语法糖async/await,使开发者很容易就能编写异步代码。
零散看过很多文章,很多是填鸭式灌输 (有的翻译文还有偏差)。

遵守以上冷冰冰的②③条的原则,一般可确保异步程序按预期运作,

我们时常能在各大论坛看到同学们(因不遵守②③点)引发的死锁现场。

由async/await引起的死锁现场

UI程序(WinForm、WPF):点击按钮,触发一个HTTP请求,用请求结果修改UI控件,以下代码会引发deadlock

public static async Task<string> GetJsonAsync(Uri uri)
{using (var client = new HttpClient()){var jsonString = await client.GetStringAsync(uri);return jsonString;}
}// 上层调用方法
public void Button1_Click(...)
{var jsonTask = GetJsonAsync(...);textBox1.Text = jsonTask.Result;
}

ASP.NET web程序:从api接口发起HTTP请求,返回请求的结果,以下代码也会引发deadlock

public static async Task<string> GetJsonAsync(Uri uri)
{using (var client = new HttpClient()){var jsonString = await client.GetStringAsync(uri);return jsonString;}
}
// 上层调用方法
public class MyController : ApiController
{public string Get(){var jsonTask = GetJsonAsync(...);return jsonTask.Result;}
}

☺️ 解决以上死锁有两种编程方式:

  1. 不再混用异步/同步写法, 始终使用async/await语法糖编写异步代码

  2. 对等待的异步任务应用ConfigureAwait(false)方法

SynchronizationContext就是这类死锁的牛鼻子,大多数时候SynchronizationContext是在异步编程后默默工作,但了解这个对象对于理解sync/await工作原理、解决死锁大有裨益。

本文会解释:

  1. async/await工作机制

  2. SynchronizationContext在异步编程语法糖中的意义

  3. 示例代码为什么会deadlock

1.  await/async语法糖工作机制

微软提出Task线程包装类、 await/async语法糖简化了异步编程的方式:

第②步:调用异步方法GetStringAsync时,开启异步任务;

第⑥步:遇到await关键字,框架会捕获调用线程的同步上下文(SynchronizationContext)对象, 附加给异步任务;同时控制权上交到上层调用函数;

第⑦步:异步任务完成,通过IO完成端口通知上层线程, 第⑧步:通过捕获的线程同步上下文执行后继代码块;

2. SynchronizationContext的意义

先看下MSDN中关于SynchronizationContext的定义:

提供在各种同步模型中传播同步上下文的基本功能。此类实现的同步模型的目的是允许公共语言运行库的内部异步/同步操作使用不同的同步模型正常运行。

☹️这就不是人能看懂的解释,我给出的解释是:在线程切换过程中保存调用线程的上下文环境, 用于在异步任务完成后使用此线程同步上下文执行后继代码。

线程同步上下文的意义在哪?

大家都知道:WinForm和WPF都有类似的原则:长耗时的任务在后台进行,将异步结果返回给UI线程 。(这难道就是ConfigureAwait方法默认传true的原因?)

此时就需要捕获UI线程的SynchronizationContext,并将这个对象传入异步任务。

public static void DoWork()
{//On UI threadvar sc = SynchronizationContext.Current;ThreadPool.QueueUserWorkItem(delegate{//... async task:do work on ThreadPool        sc.Post(delegate{// do work on the original context (UI)}, null);});
}

SynchronizationContext表示代码运行的线程环境,在异步编程中,利用该对象切换代码执行环境。

不同的.NET框架因各自独特的线程切换场景有不同的SynchronizationContext子类(重写父类虚方法):

  • ASP.NET有AspNetSynchronizationContext

  • WinForm有WindowsFormSynchronizationContext

  • WPF 有DispatcherSynchronizationContext

  • ASP.NET Core、控制台程序不存在SynchronizationContext,SynchronizationContext.Current=null

AspNetSynchronizationContext维护了HttpContext.Current、用户身份和文化,但在ASP. NET Core这些信息天然依赖注入,故不再需要SynchronizationContext;另一个好处是不再获取同步上下文对性能也是一种提升。

因此,对于ASP.NET Core程序,ConfigureAwait(false)不是必需的,然而,在基础库时最好还是使用ConfigureAwait(false),因为你保不准上层会混用同步/异步代码。

3. 引言代码为什么发生deadlock

观察引言代码,控制权返回到上层调用函数时,执行流使用Result/(Wait方法)等待任务结果:Result/Wait()导致调用线程同步阻塞(等待任务完成), 而异步任务执行完成后,会尝试利用捕获的同步上下文执行后继代码,这样形成死锁。

正因为如此,我们提出两种方式解决死锁:

  • 原调用函数始终使用await方法,这样调用线程是异步等待任务完成,后继代码可以在该线程同步上下文上执行

  • 对异步任务应用ConfigureAwait(false)方法

ConfigureAwait(bool):true  表示尝试在捕获的原调用线程SynchronizationContext 中执行后继代码;false 不再尝试在捕获的线程SynchronizationContext中执行后继代码。 ConfigureAwait(false)  能解决[因调用线程同步阻塞]引发的死锁,但是同步阻塞没有利用异步编程的优点,不是很推荐。

归根到底,这两种解决死锁的方式都是针对SynchronizationContext
ASP. NET Core和控制台程序,因为捕获的SynchronizationContext=null, 会选择一个线程同步上下文来执行,不会死锁。

总结

微软为加快开发效率上着实费了心力,.NET提供的await/async语法糖简化了异步编程方式,

在异步编程中,SynchronizationContext决定了后继代码在哪里执行的环境,深入理解这个对象的背景和不同框架的实现方式,能帮助我们避免编写死锁代码。

# 更多精彩

  • 面试八股文:你写过自定义任务调度器吗?

  • 你管这叫"线程安全"?

  • 墙裂推荐:这可能是CAP理论的最好解释

  • 全网最通透的“闭包”认知 · 跨越语言

  • 鹅厂二面,Nginx回忆录

  • 实话实说:只会.NET,会让我们一直处于鄙视链、食物链的下游

  • 什么是云原生?

今天因为你的点赞,让我元气满满!

看过这么多爆文,依旧走不好异步编程这条路?​相关推荐

  1. 我是如何走上前端开发这条路 并常年保持一线竞争力的

    我是怎么走上前端开发这条路? 首先,我是个文科生,大学里只学过vb,觉得计算机编程这东西太玄乎,不是我玩得转的. 后来机缘巧合去做了一家互联网创业公司的HR,阅了上千份程序员的简历,面了上百个不同水平 ...

  2. 蓦然回首,我是如何走上数据库开发这条路的?(二)

    生活哪里是一层一层上台阶或下台阶,生活分明是踩着一块浮冰去另一块浮冰,却永不知岸在何处. by 吕彦妮 蓦然回首,我是如何走上数据库开发这条路的?(一) 走过千山万水只为努力改变自己的命运,却不曾想生 ...

  3. 500个爆文标题_看了1000个爆文标题,终于发现了这个规律.......

    文章要爆,标题要好,这不仅仅是一句口号而已! 爆文标题有何套路,什么样标题在自媒体领域更受欢迎?小易MM吭哧吭哧的从易撰爆文库搬来了1000个爆文标题数据做研究,今天就一起与大家来说说爆文标题的那些事 ...

  4. 蓦然回首,我是如何走上数据库开发这条路的?(一)

    --我们最终都要远行,最终都要跟稚嫩的自己告别.by 海子 我只是一个普普通通的数据库开发工程师,从象牙塔离开的那一刻起就开始在社会这洪流中艰难前行.如今看着挂在胸前的工牌,思绪万千--这枚小小的工牌 ...

  5. 计算机专业一定要走编程这条路吗?其实不然,你可以这样选择!

    计算机专业出身的童鞋也可以有不同方向的发展,除了以后当程序员,你还可以有更多选择! 可能每个人爱好不一样,有的人就比较喜欢编程之类的东西,但是有的同学就太适合编程.计算机专业出身的同学大致有两个方向的 ...

  6. 从Java架构体系到BATJ面试总结,看他是怎样用1年走完别人5年的路

    介绍一下,这一次笔者笔下的这位大牛,lison,复旦大学工程硕士,专注技术十年,产品控.代码控,拥有丰富的项目经验,主持研发了多个成功上线的大型互联网项目.热爱互联网,热衷于各种web技术,精通JAV ...

  7. 怎么更好的在php上走得更远,我是如何走上PHP开发这条路

    从刚开始接触PHP到现在不知不觉已八年有余,然往事历历在目,最开始为了学习PHP以致在宿舍中通宵看视频的情景恍如昨日,一台电脑.一个耳机.一盏台灯伴我走过了几百个个日日夜夜,回想起来真是"往 ...

  8. 我是如何走上Java开发这条路的

    文章目录 一 写在前头 二 什么是Java 三 什么是面向对象 四 什么是抽象 五 什么是封装 六 什么是继承 七 什么是多态 七 Java能做什么 八 Java延申的方向 九 Java学习路线 十 ...

  9. 华为高管:苹果是个软件公司,华为也开始走这条路

    文 | 锤子 出品 | 网易科技<态℃>栏目组 HDC,全称Huawei Developer Conference,华为开发者大会. 2020年9月10日-12日,2020年的HDC在东莞 ...

最新文章

  1. Nat. Methods | ilastik:为生物图像分析而生的交互式机器学习平台
  2. Oracle Database 12c(12.1) Beta已经开始内部测试
  3. svn仓库出现already locked error解决方法:cleanup
  4. php 类分开写,程序员面试必备PHP基础面试题 – 第十七天
  5. 在SpringBoot中使用redis实现分布式锁
  6. Spark配置属性整理(非常全面)
  7. java与java ee_Java EE 8 MVC:全局异常处理
  8. GCF(2)---GCF中LAUNCH BROWSER测项注意事项
  9. 《如何搭建小微企业风控模型》第十三节 额度公式 节选
  10. 【Qt】使用QProcess调用其它程序或脚本
  11. 6 VPP源码分析 (VPP中的多线程)
  12. 第十八篇 难点突破之-VUE中使用 C-LODOP 实现模板的套打
  13. Python chardet
  14. 计算机上的无线网络开关怎么打开,联想笔记本无线网络开关怎么打开
  15. TI DSP各种库文件
  16. (译)Xposed模块开发教程
  17. js ---实现排他效果
  18. java 计算机概述看这一篇文章就够了
  19. 【tph-yolov5】tph-Yolov5的Pytorch环境配置和运行错误记录
  20. 用PHP实现MD5算法

热门文章

  1. DOM Xerces类库使用方法
  2. 帆软报表(finereport)table块钻取,返回记住table块位置
  3. 利用解构赋值获取后端特定字段数据
  4. SQLServer Agent执行[分发清除: distribution] 无法删除快照文件
  5. 1Android系统移植与驱动开发概述
  6. hadoop:could only be replicated to 0 nodes, instead of 1
  7. 二 ASP.NET AutoPostBack
  8. System.Xml名称空间下的支持DOM的类型
  9. 在Teams app代码中enable nullable
  10. 基于redis实现的扣减库存