其实好久以前就看过这个文章,以及类似的很多篇文章。最近在和一个新同事的交流中发现原来对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 最佳实践的更多相关文章

  1. .NET Async/Await 最佳实践

    .NET 异步编程Guildlines 名称 描述 例外 Avoid async void Prefer async Task methods over async void methods Even ...

  2. 译文: async/await SynchronizationContext 上下文问题

    async / await 使异步代码更容易写,因为它隐藏了很多细节. 许多这些细节都捕获在 SynchronizationContext 中,这些可能会改变异步代码的行为完全由于你执行你的代码的环境 ...

  3. 将 async/await 异步代码转换为安全的不会死锁的同步代码

    在 async/await 异步模型(即 TAP Task-based Asynchronous Pattern)出现以前,有大量的同步代码存在于代码库中,以至于这些代码全部迁移到 async/awa ...

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

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

  5. 关于ES7中的async/await在客户端和服务端上的实践

    一.前言 在项目中经常遇到处理异步请求的情况,面对层层的嵌套,回调显示那么苍白无力: async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案,既然这样就用上吧. 二.配 ...

  6. 【转】C# Async/Await 异步编程中的最佳做法

    Async/Await 异步编程中的最佳做法 Stephen Cleary 近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支 ...

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

    背景 我们的小程序项目的构建是与web项目保持一致的,完全使用webpack的生态来构建,没有使用小程序自带的构建功能,那么就需要我们配置代码转换的babel插件如Promise.Proxy等:另外, ...

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

    又到了周末的code review环节,这次code review发现了一个对async/await的理解问题.让我们直奔主题: var foodsSearch = new FoodSearchSer ...

  9. C#中async/await中的异常处理

    在同步编程中,一旦出现错误就会抛出异常,我们可以使用try-catch来捕捉异常,而未被捕获的异常则会不断向上传递,形成一个简单而统一的错误处理机制.不过对于异步编程来说,异常处理一直是件麻烦的事情, ...

随机推荐

  1. java socket client

    用tornado做了个socket server.无奈联调的人员对接不上. 于是撸出了以下demo import java.io.*; import java.net.*; public class ...

  2. PHP的反射类ReflectionClass、ReflectionMethod使用实例

    PHP5 具有完整的反射API,添加对类.接口.函数.方法和扩展进行反向工程的能力. 反射是什么? 它是指在PHP运行状态中,扩展分析PHP程序,导出或提取出关于类.方法.属性.参数等的详细信息,包括 ...

  3. Clone Graph

    Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors. OJ's ...

  4. bccomp比较大小注意

    2015年12月15日 14:18:56 星期二 echo bccomp('1', '1.01', 2); // -1 echo bccomp('1', '1.01', 3); // -1 echo ...

  5. [转载]Linux命令笔记

    *以下内容均来自于网络转载,感谢原作者分享 <对Linux新手非常有用的20个命令> 传送门 英文原文为“Switching From Windows to Nix or a Newbie ...

  6. Ubuntu jsp平台使用JDBC来连接MySQL数据库

    Ubuntu 7.04 搭建Ubuntu jsp平台开发环境MySQL+tomcat+apache+j2sdk1.6在所有安装开始前先在Terminal中输入 rpm -q -a查看是否安装过rpm ...

  7. jQueryUI Datepicker的使用

    jQueryUI Datepicker是一个高度可定制的插件,可以很方便的为你的页面添加日期选择功能,你可以自定义日期的显示格式 以及要使用的语言. 你可以使用键盘的快捷键来驱动datepicker插 ...

  8. 【leetcode】Partition List(middle)

    Given a linked list and a value x, partition it such that all nodes less than x come before nodes gr ...

  9. 20145213《Java程序设计》第十周学习总结

    20145213<Java程序设计>第十周学习总结 教材学习总结 网络编程 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据.程序员所作的事情就是把数据发送到指定的位置,或者接 ...

  10. struts2截取字符串

    <struts:if test="null!=pushAd&&pushAd.length()>14"> <struts:property v ...