Async/Await 最佳实践

其实好久以前就看过这个文章,以及类似的很多篇文章。最近在和一个新同事的交流中发现原来对async的死锁理解不是很透彻,正好最近时间比较充裕就再当一回搬运工。

本文假定你对.NET Framework 4.5 的异步编程有基本的了解,相关的建议你能够在Stack Overflow, MSDN 以及async/awai FAQ中找到。这里并不试图想讲述多少新的知识点,而是想强调几点最佳实践,以减少你阅读大量文档排查问题的时间。本文的最佳实践更多的是一些指导意见,并不是实际意义上的规则。每条意见后面都有一些例外情况,这里都已经一一列出了,具体情况下面我们分条讲解。


规则


描述


例外


避免使用 async void


优先使用 async Task 而不用 async void


Event handlers


Async到顶


不要混合使用 blocking 和 async 的代码


Console main method


注意配置好执行的context


尽量设置 ConfigureAwait(false)


需要context的除外

避免使用 async void

Task 和 Task<T> 是异步方法的通常返回值类型。在将同步代码改造成async方式过程中,我们将void 类型返回值改成async Task.

其实,这里你改写成async void 编译器不会报错。事实上,async void 存在的意义仅仅是使用在event handlers上。如果你在普通的代码中使用async void,该代码块中的异

常处理就会变得比较麻烦。

可以看到,上面的Exception 无法被直接catch. 当然你可以使用AppDomain.UnhandledException等类似方法去捕捉该异常,但是这种代码维护起来就比较麻烦。另外,Task和Task<T>能够使用await, Task.WhenAny, Task.WhenAll等方式组合使用。Async Void 不行。同时Async void 方式比较难测试,具体原因可以参考原文。

让Async传染到顶。

这里可以称呼为async具有僵尸病毒般的传染性,async 会感染周围的代码,直到顶层。其实我们只需要顺其自然,让所有代码都传染上异步特性即可。如果我们使用Task.Wait 或者Task.Result去试图阻塞async 的传播,往往便会自找苦吃。这个往往是刚接触async 异步的人最容易犯的错误,这些人往往试图将async 方法做个小小包装搞成同步的方法,以达到不修改旧代码的目的。不幸的是,这样往往会导致死锁。这个死锁的问题在MSDN,StackOverFlow已经烂大街了。

如上所示,重现这个死锁实在是太容易了。理解该死锁的原因在于理解await 处理contexts的方式。默认的,当一个未完成的Task 被await的时候,当前的上下文将在该Task完成的时候重新获得并继续执行剩余的代码。这个context就是当前的SynchronizationContext ,除非它是空的。GUI和ASP.NET 应用程序的SynchronizationContext 有排他性,只允许一个线程运行。当await 完成的时候,它试图在它原来的代码上下文执行它剩余的部分,但是该代码上下文中已经有一个线程在了,就是那个一直在同步等待async 完成的那个线程,它们两个相互等待,因此就死锁了。

注意控制台程序不会导致死锁。控制台的SynchronizationContext 是类似于一个线程池的机制而不是排他的。因此当await 结束的时候,它可以重新获得原来的上下文然后执行完剩余代码并返回成功。这个不同是很多人产生困惑的根源,当他们在控制台测试的时候程序跑的好好的,拷贝到GUI或者ASP.NET 程序中就发生了死锁。

因此,最佳的解决方案就是允许async 自动传染直到最上层,通常到入口点为止。控制台的Main 方法无法被标注成async,原因你懂的。因此Main 方法这里是个例外。

通常全套异步需要做一些额外的工作,下面是一些必须做的额外工作。



不要使用


使用


需要获得值的时候


Task.Wait or Task.Result


await


需要等待任何一个任务


Task.WaitAny


await Task.WhenAny


需要等待所有任务完成


Task.WaitAll


await Task.WhenAll


需要等待


Thread.Sleep


await Task.Delay

     

当然如果你不打算全套使用async, 那么请必须注意处理好异常信息以及防止死锁的一些方法。

注意配置好执行的context

配置好continueOnCapturedContext为false可以提高性能。这个我建议直接看代码。

我们将task 配置为不需要切换为原context, 这样后面的代码将不会在GUI 线程中运行,可以减少对性能的影响。除了性能有一点点提升以外,设置好context 还能够避免死锁。当然这种解决方式需要注意的点实在太多,稍不注意就又死锁了。

同步调用异步async防止死锁方案

因此网上有人给出了以下比较通用的解决方案。具体代码就不贴全了,大概思路就是对上下文的处理,以及对一些异常信息的包装等,需要代码的同学自己去翻。

参考:

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

时间: 2024-10-12 22:42:24

Async/Await 最佳实践的相关文章

.NET Async/Await 最佳实践

.NET 异步编程Guildlines 名称 描述 例外 Avoid async void Prefer async Task methods over async void methods Event handlers Async all the way Don’t mix blocking and async code Console main method Configure context Use ConfigureAwait(false) when you can Methods th

vue中使用async/await的实践

一.前言 在项目中经常遇到处理异步请求的情况,面对层层的嵌套,回调显示那么苍白无力: async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案,既然这样就用上吧. 二.配置编译 网上实践很多... 反倒是Promise在实践过程中加了一个polyfill. 三.实践 await后方法要写成peomise的形式,大概如下,这样异步操作看起来就像同步操作一样明了. 原文地址:https://www.cnblogs.com/leaf930814/p/8502541.htm

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

近日来,涌现了许多关于 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 这类在线资源提供了同样的建议. 本文只重点介绍一些淹没在文档海洋中的最佳做法. 本文中的最佳做法更大程度上是“指导原则”,而不是实际规则. 其中每个指导原则都有一些例外情况

微信小程序捕获async/await函数异常实践

背景 我们的小程序项目的构建是与web项目保持一致的,完全使用webpack的生态来构建,没有使用小程序自带的构建功能,那么就需要我们配置代码转换的babel插件如Promise.Proxy等:另外,项目中涉及到异步的功能我们统一使用async/await来处理.我们知道,小程序的onError 生命周期只能捕获同步错误,而完全不采用小程序自带构建工具的情况下,开发模式下遇到的问题: 小程序异步代码中的异常onError无法捕获,开发者工具控制台也没有抛出异常信息 这样在开发过程中页面展示异常,

你眼中的async/await是什么样的呢?

又到了周末的code review环节,这次code review发现了一个对async/await的理解问题.让我们直奔主题: var foodsSearch = new FoodSearchService().Search(); var fruitsSearch = new FruitSearchService().Search(); var foods = await foodsSearch; foods.ForEach(f => Console.WriteLine("food:{0

Dependency injection in .NET Core的最佳实践

我们知道依赖注入(DI)是一种实现对象及其协作者或依赖关系之间松散耦合的技术. ASP.NET Core包含一个简单的内建容器来支持构造器注入. 我们试图将DI的最佳实践带到.NET Core应用程序中,这表现在以下方面: 构造器注入 注册组件 DI in testing 构造器注入 我们可以通过方法注入.属性注入.构造器注入的方式来注入具体的实例,一般来说构造器注入的方式被认为是最好的方式,所以在应用程序中将使用构造器注入,请避免使用别的注入方式.一个构造器注入的例子如: public cla

[译]async/await中使用阻塞式代码导致死锁

原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的两篇博文中翻译过来. 原文1:Don'tBlock on Async Code 原文2:why the AspNetSynchronizationContext was removed 示例代码:async_await中使用阻塞式代码导致死锁.rar 一.async/await 异步代码运行流

Vue 工程化最佳实践

目录结构 总览 api 目录用于存放 api 请求,文件名与模型名称基本一致,文件名使用小驼峰,方法名称与后端 restful 控制器一致. enums 目录存放 常量,与后端的常量目录对应 icons 目录用于存放图标,element-ui 提供的图标实在是太少啦.所以我通常会使用 阿里的 iconfont lang 目录存放多语言 layouts 目录存放布局 上面展示的是一个后台系统,empty 为一个空布局.用于登录页面,其他页面则使用 default 布局.布局不需要过多介绍,写过 l