文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-03 04:11

关注

引言

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

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

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

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

由async/await引起的死锁现场

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

  1. public static async Task GetJsonAsync(Uri uri) 
  2.   using (var client = new HttpClient()) 
  3.   { 
  4.     var jsonString = await client.GetStringAsync(uri); 
  5.     return jsonString; 
  6.   } 
  7.  
  8. // 上层调用方法 
  9. public void Button1_Click(...) 
  10.   var jsonTask = GetJsonAsync(...); 
  11.   textBox1.Text = jsonTask.Result; 

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

  1. public static async Task GetJsonAsync(Uri uri) 
  2.   using (var client = new HttpClient()) 
  3.   { 
  4.     var jsonString = await client.GetStringAsync(uri); 
  5.     return jsonString; 
  6.   } 
  7. // 上层调用方法 
  8. public class MyController : ApiController 
  9.   public string Get() 
  10.   { 
  11.     var jsonTask = GetJsonAsync(...); 
  12.     return jsonTask.Result; 
  13.   } 
  1. 不再混用异步/同步写法, 始终使用async/await语法糖编写异步代码
  2. 对等待的异步任务应用ConfigureAwait(false)方法

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

本文会解释:

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

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

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

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

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

2. SynchronizationContext的意义

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

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

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

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

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

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

  1. public static void DoWork() 
  2.     //On UI thread 
  3.     var sc = SynchronizationContext.Current
  4.  
  5.     ThreadPool.QueueUserWorkItem(delegate 
  6.     { 
  7.         //... async task:do work on ThreadPool 
  8.         sc.Post(delegate 
  9.         { 
  10.              // do work on the original context (UI) 
  11.         }, null); 
  12.     }); 

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

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

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

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

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

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

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

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

归根到底,这两种解决死锁的方式都是针对SynchronizationContext;

ASP. NET Core和控制台程序,因为捕获的SynchronizationContext=null, 会选择一个线程同步上下文来执行,不会死锁。

总结

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

 

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

 

来源:精益码农 内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯