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
Async/Await 最佳实践的更多相关文章
- .NET Async/Await 最佳实践
.NET 异步编程Guildlines 名称 描述 例外 Avoid async void Prefer async Task methods over async void methods Even ...
- 译文: async/await SynchronizationContext 上下文问题
async / await 使异步代码更容易写,因为它隐藏了很多细节. 许多这些细节都捕获在 SynchronizationContext 中,这些可能会改变异步代码的行为完全由于你执行你的代码的环境 ...
- 将 async/await 异步代码转换为安全的不会死锁的同步代码
在 async/await 异步模型(即 TAP Task-based Asynchronous Pattern)出现以前,有大量的同步代码存在于代码库中,以至于这些代码全部迁移到 async/awa ...
- 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法
什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...
- 关于ES7中的async/await在客户端和服务端上的实践
一.前言 在项目中经常遇到处理异步请求的情况,面对层层的嵌套,回调显示那么苍白无力: async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案,既然这样就用上吧. 二.配 ...
- 【转】C# Async/Await 异步编程中的最佳做法
Async/Await 异步编程中的最佳做法 Stephen Cleary 近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支 ...
- 微信小程序捕获async/await函数异常实践
背景 我们的小程序项目的构建是与web项目保持一致的,完全使用webpack的生态来构建,没有使用小程序自带的构建功能,那么就需要我们配置代码转换的babel插件如Promise.Proxy等:另外, ...
- 你眼中的async/await是什么样的?
又到了周末的code review环节,这次code review发现了一个对async/await的理解问题.让我们直奔主题: var foodsSearch = new FoodSearchSer ...
- C#中async/await中的异常处理
在同步编程中,一旦出现错误就会抛出异常,我们可以使用try-catch来捕捉异常,而未被捕获的异常则会不断向上传递,形成一个简单而统一的错误处理机制.不过对于异步编程来说,异常处理一直是件麻烦的事情, ...
随机推荐
- Droid4x设置代理抓包
Droid4x也是基于virtualbox+x86架构的 代理设置 设置->WIFI->鼠标按住WiredSSID选项不放->修改网络->显示高级选项->代理-> ...
- 【MySQL】MySQL server has gone away 怎么处理?
直接上代码: from django.db import connection ... def is_connection_usable(): try: connection.connection.p ...
- 【GoLang】GoLang 单元测试、性能测试使用方法
单元测试代码: ackage test import ( // "fmt" "testing" ) func Test_FlowControl(t *testi ...
- Group Anagrams
Given an array of strings, group anagrams together. For example, given: ["eat", "tea& ...
- 数位DP题目汇总
Google Code Jam 2014 Round 1B Problem B hdu 2089 hdu 3555 uestc 250 (原1307) hdu 3652 hdu 3709 Light ...
- Effective C++ -----条款48:认识template元编程
Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率. TMP可被用来生成“基于政策选择组合”(based on ...
- EF查询视图只得到一条记录
1.出错结果:数据库表视图有多条数据,在使用EF框架进行查询时却只得到一条数据(注:拦截EF得到的sql语句在数据库进行查询并没有任务问题). 2.出错原因:该视图中没有ID或者主键,EF查询时进行反 ...
- quartz+spring 实现多任务动态定时器问题
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w ...
- sql语句按照汉字拼音首字母排序
oracle : 在oracle9i中新增了按照拼音.部首.笔画排序功能.设置NLS_SORT值SCHINESE_RADICAL_M 按照部首(第一顺序).笔划(第二顺序)排序SCHINESE_STR ...
- 好用的php类库和方法
1, /** * 将一个平面的二维数组按照指定的字段转换为树状结构 * * 用法: * @code php * $rows = array( * array('id' => 1, 'value' ...