长话短说,本文带大家抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext

引言

C#异步编程语法糖async/await,使开发者很容易就能编写异步代码。

零散看过很多文章,很多是填鸭式灌输 (有的翻译文还有偏差)。



遵守以上冷冰冰的②③条的原则,可以确保我们的异步程序按照预期运作,但是我们常看到违背这2条原则引发的死锁现场。

由async/await引起的死锁现场

UI例子:

点击按钮触发一个HTTP请求,用请求的返回值修改UI控件, 以下代码会引发deadlock (类似状态出现在WinForm、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;
}

ASP.NET web例子:

从api发起远程HTTP请求,等待请求的结果,以下代码也会引发deadlock

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

️ 解决以上死锁有2种编程写法:

  1. 不再混用异步、同步写法, 始终使用async/await语法糖编写异步代码
  2. 在等待的异步任务时应用ConfigureAwait(false)方法

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

本文会解释:

  1. async/await工作机制
  2. SynchronizationContext在异步编程语法糖中的意义
  3. 为什么会有deadlock

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

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

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

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

第⑦步:异步任务完成,通过IO完成端口通知上层线程,

第⑧步:通过捕获的线程同步上下文执行后继代码块;

2.SynchronizationContext的意义

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

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

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

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

我们大家都知道:WinForm和WPF都有类似的原则: 长耗时的任务在后台计算,将异步结果返回给UI线程

这个时候我们就需要捕获UI线程的SynchronizationContext,并将这个对象传入后台线程。

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子类(通常是重写父类虚方法):

  • ASP.NET有AspNetSynchronizationContext
  • Windows Form有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决定了后继代码在哪里执行的环境,深入理解这个对象的背景和不同框架的实现方式,能帮助我们避免编写死锁代码。

抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext的更多相关文章

  1. 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法

    什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...

  2. 重新认识 async/await 语法糖

    提起.Net中的 async/await,相信很多.neter 第一反应都会是异步编程,其本质是语法糖,但继续追查下去,既然是语法糖,那么经过编译之后,真正的代码是什么样的,如何执行的?带着这些疑问, ...

  3. 异步编程Async/await关键字

    异步编程Async \await 关键字在各编程语言中的发展(出现)纪实. 时间 语言版本 2012.08.15 C#5.0(VS2012) 2015.09.13 Python 3.5 2016.03 ...

  4. javascript异步编程 Async/await

    Async/await Async/await 在学习他之前应当补充一定的 promise 知识 它是一种与 promise 相配合的特殊语法,目前被认为是异步编程的终级解决方案 值得我们每一个人学习 ...

  5. .net 温故知新:【5】异步编程 async await

    1.异步编程 异步编程是一项关键技术,可以直接处理多个核心上的阻塞 I/O 和并发操作. 通过 C#.Visual Basic 和 F# 中易于使用的语言级异步编程模型,.NET 可为应用和服务提供使 ...

  6. [C#] 谈谈异步编程async await

    为什么需要异步,异步对可能起阻止作用的活动(例如,应用程序访问 Web 时)至关重要. 对 Web 资源的访问有时很慢或会延迟. 如果此类活动在同步过程中受阻,则整个应用程序必须等待. 在异步过程中, ...

  7. .net 异步编程async & await关键字的思考

    C# 5.0引入了两个关键字 async和await,这两个关键字在很大程度上帮助我们简化了异步编程的实现代码,而且TPL中的task与async和await有很大的关系 思考了一下异步编程中的asy ...

  8. 异步编程async/await

    什么是异步? 在异步程序中,程序代码不需要按照编写时的顺序严格执行,有时需要一在一个新的线程中运行一部分代码,有时无需创建新的 线程,但是为了更好的利用单个线程的能力,需要改变代码的执行顺序. 进程 ...

  9. c#异步编程async await

    可以代替协程了 但是需要.net4 版本 unity2017以上版本可以用了 再也可以不用蛋疼的没有返回值的协程了 //异步编程,和Task一起用 async void TestAsync(){ // ...

随机推荐

  1. JDBC_05_ResorceBundle(资源绑定器) 绑定配置文件

    ResorceBundle(资源绑定器) 绑定配置文件 jdbc.proprtise 需要在src目录下新建一个文件夹然后将jdbc.proprtise放在文件中然后右键该文件夹选择 Rebuild ...

  2. UI自动化测试框架:PO模式+数据驱动

    1. PO 设计模式简介 2. 工程结构说明 3. 工程代码实现 page 包 action 包 business_process 包 util 包 conf 包 test_data 目录 log 目 ...

  3. MySQL数据库干货分享!mysql每月自动创建表结构

    如果你刚好在学MySQL,博主推荐一套很详细的MySQL教程 主要详细讲解了MySQL的相关知识,包括MySQL概述,MySQL应用环境,MySQL系统特性,MySQL初学基础,MySQL管理工具,如 ...

  4. Springboot+Vue前后端分离的博客项目

    项目介绍 演示站(服务器已过期):http://blog.hanzhe.site 开源项目地址 ( 求给个Star ):https://gitee.com/zhang_hanzhe/blog 前端采用 ...

  5. CentOS7 常用基础操作

    系统目录结构了解 CentOS系统中没有磁盘的概念,一切皆文件,/目录下的的一个个文件夹目录就相当于磁盘了,这里简单记录几个常用的目录以及对应的作用: dev:Linux一切皆文件,包括硬件也进行了文 ...

  6. OO Unit2 总结

    OO Unit2 总结 OO课Unit2电梯仿真项目技术回顾 BUAA.1823.邓新宇 2020/4/17 Part1 设计策略 从多线程的协同和同步控制方面,分析和总结自己三次作业的设计策略 第一 ...

  7. 硬件篇-03-SLAM移动底盘电气设计

      最近因为在忙毕设,专栏已经1个多月没更,对于托更我很抱歉.不过这几周真的没什么时间,Rick&Morty的最新集我到现在都还没看哈哈.     现在毕设已经搞得差不多了,水专栏文章的快乐生 ...

  8. 关于js中的回调函数callback,通俗易懂

    前言 其实我一直很困惑关于js 中的callback,困惑的原因是,学习中这块看的资料少,但是平时又经常见,偶尔复制一下前人代码,功能实现了也就不再去追其原由,这么着,这个callback的概念就越来 ...

  9. php读取目录下的所有文件

    php读取目录下的所有文件 $path = './use'; $result = scanFile($path); function scanFile($path) { global $result; ...

  10. SSDT表概念详解

    SSDT 的全称是 System Services Descriptor Table,系统服务描述符表. 这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来 ...