探究SynchronizationContext在.Net异步编程中的地位

原文:探究SynchronizationContext在.Net异步编程中的地位

引言:

  多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类await/async异步编程语法糖简化了异步编程方式。

相信很多开发者都看到如下异步编程实践原则:

  实践原则  说明  例外情况
 ①  避免 Async Void  最好使用 async Task 方法而不是 async void 方法  事件处理程序
 ②  始终使用 await  不要混合阻塞式代码和异步代码  控制台 main 方法
 ③  配置上下文  尽可能使用ConfigureAwait(false)  需要上下文的方法

  遵守以上冷冰冰的②③条的原则,可保证异步程序按照预期状态正常运作;我们在各大编程论坛常看到违背这2条原则引发的莫民奇妙的死锁问题。

  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;
}

  ASP.NET例子:API Action发起远程HTTP请求,等待请求的json结果,并解析json字符串,以下代码也会引发deadlock

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 密切相关,大多数时候SynchronizationContext 是在异步编程后面默默工作的, 但是了解这个对象对于理解Task、await/sync 工作原理大有裨益。本文会解释

  • 为什么要有SynchronizationContext 对象
  • 阐述await关键字与SynchronizationContext对象交互原理
  • 以上代码为什么会有deadlock, 另外ASP.NET Core为什么不会发生以上死锁

1. The Need for SynchronizationContext

  先看下MSDN中关于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 的关系?

  以上ThreadPool.QueueUserWorkItem 涉及线程底层,微软提出Task线程包装类和 await/async 简化了异步编程的方式:

  

  ① 调用异步方法GetStringAsync时,.NET框架为我们创建了异步任务T;

  ② 应用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在线程切换后帮我们有能力执行各种骚操作。

原文地址:https://www.cnblogs.com/lonelyxmas/p/10646653.html

时间: 2024-10-06 12:55:52

探究SynchronizationContext在.Net异步编程中的地位的相关文章

Async/Await 异步编程中的最佳做法

近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支持的信息. 本文旨在作为学习异步编程的“第二步”:我假设您已阅读过有关这一方面的至少一篇介绍性文章. 本文不提供任何新内容,Stack Overflow.MSDN 论坛和 async/await FAQ 这类在线资源提供了同样的建议. 本文只重点介绍一些淹没在文档海洋中的最佳做法. 本文中的最佳做法更大程度上是“指导原则”,而不是实际规则. 其中每个指导原则都有一些例外情况

异步编程中的最佳做法

原文链接 近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支持的信息. 本文旨在作为学习异步编程的“第二步”:我假设您已阅读过有关这一方面的至少一篇介绍性文章. 本文不提供任何新内容,Stack Overflow.MSDN 论坛和 async/await FAQ 这类在线资源提供了同样的建议. 本文只重点介绍一些淹没在文档海洋中的最佳做法. 本文中的最佳做法更大程度上是“指导原则”,而不是实际规则. 其中每个指导原则都有一

异步编程中的最佳做法(Async/Await) --转

近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支持的信息. 本文旨在作为学习异步编程的“第二步”:我假设您已阅读过有关这一方面的至少一篇介绍性文章. 本文不提供任何新内容,Stack Overflow.MSDN 论坛和 async/await FAQ 这类在线资源提供了同样的建议. 本文只重点介绍一些淹没在文档海洋中的最佳做法. 本文中的最佳做法更大程度上是“指导原则”,而不是实际规则. 其中每个指导原则都有一些例外情况

一种C# TCP异步编程中遇到的问题

最近在维护公司的一个socket服务端工具,该工具主要是提供两个socket server服务,对两端连接的程序进行数据的透明转发. 程序运行期间,遇到一个问题,程序的一端是GPRS设备,众所周知,GPRS设备的网络连接十分的不问题,由此会产生不少的"奇怪"问题.实际过程中,程序运行几个小时后,无线端的socket server断开就再也无法打开.找了很久都没发现. 通过wireshark抓取通信报文,一般是在TCP的三次握手时出的问题.常规的TCP三次握手,由TCP的标识可简单看作:

你不知道的this—JS异步编程中的this

Javascript小学生都知道了javascript中的函数调用时会 隐性的接收两个附加的参数:this和arguments.参数this在javascript编程中占据中非常重要的地位,它的值取决于调用的模式.总的来说Javascript中函数一共有4中调用模式:方法调用模式.普通函数调用模式.构造器调用模式.apply/call调用模式.这些模式在如何初始化关键参数this上存在差异.“可能还有小伙伴不知道它们之间的区别,那我就勉为其难撸一撸吧!” 方法调用模式:函数是在某个明确的上下文对

异步编程中的异常处理

在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行. 在async Task方法中引发的异常,存放在返回的Task对象中,只有当Task对象被await调用时,才会引发异常.因为 async void 方法没有返回 Task 对象,无法存放异常,所以做法就会不同,最好不要从 async void 方法传递出异常.如果必须使用 async void 方法,可考虑把所有代码放在 try 块中,直接处理异常.

全面解析C#中的异步编程

当我们处理一些长线的调用时,经常会导致界面停止响应或者IIS线程占用过多等问题,这个时候我们需要更多的是用异步编程来修正这些问题,但是通常都是说起来容易做起来难,诚然异步编程相对于同步编程来说,它是一种完全不同的编程思想,对于习惯了同步编程的开发者来说,在开发过程中难度更大,可控性不强是它的特点. 在.NET Framework5.0种,微软为我们系统了新的语言特性,让我们使用异步编程就像使用同步编程一样相近和简单,本文中将会解释以前版本的Framework中基于回调道德异步编程模型的一些限制以

Async in C# 5.0(C#中的异步编程Async) 蜗牛翻译之第一章

p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚. 如果你觉得这件事儿没意义翻译的又差,尽情的踩吧.如果你觉得值得鼓励,感谢留下你的赞,祝各位爱技术的园友在今后每一次应该猛烈突破的时候,不选择知难而退.在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力

.Net中的异步编程

.Net中的异步编程? .net中实现异步有两种方式:第一种是多线程的方式,第二种是使用异步函数,其实在异步函数中使用的还是多线程的技术. 异步编程中比较关注也比较重要的技术点在于:1.当异步线程在工作完成时如何通知调用线程:2.当异步线程出现异常的时候该如何处理: 3.异步线程工作的进度如何实时的通知调用线程:4.如何在调用线程中取消正在工作的异步线程,并进行回滚操作. 虽然在.net中提供了众多的异步编程模式,但是推荐最好使用Task类,因为Task类使用线程池中的任务线程,又由线程池管理,