我在 使用 Task.Wait()?立刻死锁(deadlock) 一文中站在类库使用者的角度看 async/await 代码的死锁问题;而本文将站在类库设计者的角度来看死锁问题。

阅读本文,我们将知道如何编写类库代码,来尽可能避免类库使用者出现那篇博客中描述的死锁问题。


 

可能死锁的代码

现在,我们是类库设计者的身份,我们试图编写一个 RunAsync 方法用以异步执行某些操作。

private async Task RunAsync()
{
// 某些异步操作。
}

类库的使用者可能多种多样,一个比较有素养的使用者会考虑这样使用类库:

await foo.RunAsync();

放心,这样的类库使用者是不会出什么岔子的。

然而,这世间既然有让人省心的类库使用者,当然也存在非常让人不省心的类库使用者。当你的类库遍布全球,你真的会遇到这样的使用者:

foo.RunAsync().Wait();

如果这段代码在 UI 线程执行,那么极有可能出现死锁,就是我在 使用 Task.Wait()?立刻死锁(deadlock) 一文中说的那种死锁,详情可进去看原因。

那么现在做一个调查,你认为下面三种 RunAsync 的实现中,哪些会在碰到这种不省心的类库使用者时发生死锁呢?

答案是——

第 2 种

只有第 2 种会发生死锁,第 1 和第 3 种都不会。

原因

对于第 2 种情况,下方“await 之后的代码”试图回到 UI 线程执行,但 UI 此时处于调用者 foo.RunAsync().Wait(); 这段神奇代码的等待状态——所以死锁了。回到 UI 线程靠的是 DispatcherSynchronizationContext,我在 使用 Task.Wait()?立刻死锁(deadlock) 一文中已有解释,建议前往了解更深层次的原因。

private async Task RunAsync1()
{
await Task.Run(() =>
{
// 某些异步操作。
});
// await 之后的代码(即使没写任何代码,也是需要执行的)。
}

那为什么第 1 种和第 3 种不会死锁呢?

对第 1 种情况,由于并没有写 async/await,所以异步状态机 AsyncMethodStateMachine 此时并不执行。直接返回了 Task,这相当于此时创建的 Task 对象直接被调用者的 foo.RunAsync().Wait(); 神奇代码等待了。也就是说,等待的 Task 是真正执行异步任务的 Task

TaskWait() 方法内部通过自旋锁来实现等待,可以阅读 .NET 中的轻量级线程安全 - walterlv 了解自旋锁,也可以前往 .NET Framework 源码 Task.SpinWait 了解 Task.SpinWait() 方法的具体实现。

//spin only once if we are running on a single CPU
int spinCount = PlatformHelper.IsSingleProcessor
? 1
: System.Threading.SpinWait.YIELD_THRESHOLD;
for (int i = 0; i < spinCount; i++)
{
if (IsCompleted)
{
return true;
} if (i == spinCount / 2)
{
Thread.Yield();
}
else
{
Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i));
}
}

Run 中的异步任务结束后,自旋锁即发现任务结束 Task.IsCompletedTrue,于是等待结束,不会发生死锁。

对第 3 种情况,由于指定了 ConfigureAwait(false),这意味着通知异步状态机 AsyncMethodStateMachine 并不需要使用设置好的 SynchronizationContext(对于 UI 线程,是 DispatcherSynchronizationContext)执行线程同步,而是使用默认的 SynchronizationContext,而默认行为是随便找个线程执行后面的代码。于是,await Task.Run 后面的代码便不需要返回原线程,也就不会发生第 2 种情况里的死锁问题。

预防

建议安装 NuGet 包 Microsoft.CodeAnalysis.FxCopAnalyzers。这样,当你在代码中写出 await 时,分析器会提示你 CA2007 警告,你必须显式设置 ConfigureAwait(false)ConfigureAwait(true) 来提醒你是否需要使用默认的 SynchronizationContext

如果你是类库的编写者,注意此问题能够一定程度上防止逗比使用者出现死锁问题后喷你的类库写得不好。

在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁的更多相关文章

  1. ConfigureAwait(false)

    昨天在做项目的时候,用的dapper查数据用的QueryAsync 异步方法.给上级做代码审核时,上级说最好加上ConfigureAwait(false).能减少一些性能开销. 因为之前没用过所以看了 ...

  2. 走进异步世界-犯傻也值得分享:ConfigureAwait(false)使用经验分享

    在上周解决“博客程序异步化改造之后遭遇的性能问题”的过程中,我们干了一件自以为很有成就感的事——在表现层(MVC与WebForms)将所有使用await的地方都加上了ConfigureAwait(fa ...

  3. 编写dll时的内存分配策略

    前一篇文章介绍了为何要共用内存管理器,有人要问可不可以在编写dll时更通用一些,可以兼顾其它编译器(如果是其它编译器的话,Delphi写的dll不能与其它语言共用内存管理器),采用一定的策略来避免在d ...

  4. 编写Shader时的一些性能考虑

    编写shader时的一些建议:1.只计算需要计算的东西:2.通常,需要渲染的像素比顶点数多,而顶点数又比物体数多很多.所以如果可以,尽量将运算从PS移到VS,或直接通过script来设置某些固定值:3 ...

  5. jsp编写页面时常见错误提示

    jsp编写页面时常见错误提示 404-->未部署web应用 500-->代码有问题 无法显示网页-->未启动tomcat webRoot-->URL输入有误 web-inf-- ...

  6. ConfigureAwait(false)避免上下文延续

    之前MVC利用MvcHtmlString封装通用下拉菜单,菜单数据需要从webapi获取,自然用到了 await Http Client.GetAsync(Url)方法,前端 @Html.Select ...

  7. IDEA连接Mysql数据库之后,在Mapper.xml编写SQL时不会自动提示表信息问题(非常详细!)

    1.首先得连接上数据库 (一)点击IDEA右侧数据库模块 (二)选择MySql进行连接 (三)填写数据库相关配置 (四)重点!!! 这个时候点击测试连接是连接不上的,需要设置时区 (按照如下设置) ( ...

  8. github 编写README时常用的写法

    参考:https://github.com/HeTingwei/ReadmeLearn#%E7%BC%96%E5%86%99readme%E6%97%B6%E5%B8%B8%E7%94%A8%E7%9 ...

  9. 编写nios-shell时想到的问题-回车vs换行

    在编写nios上类shell用户交互代码时.由于要检測终端输入字符.所以想到了这个问题,故分析之. 回车符的ascii码,ASCII码13 '\r' 换行符的ascii码.ASCII码10 '\n' ...

随机推荐

  1. (转)全面认识一下.NET 4的缓存功能

    很多关于.NET 4.0新特性的介绍,缓存功能的增强肯定是不会被忽略的一个重要亮点.在很多文档中都会介绍到在.NET 4.0中,缓存功能的增强主要是在扩展性方面做了改进,改变了原来只能利用内存进行缓存 ...

  2. LR--实现HTTP协议的接口测试

    场景分析:使用LR完成HTTP协议的接口测试 流程: 1.首先需要找一个接口(POST.GET接口) 2.LR中点击Insert-->New Step-->web_custom_reque ...

  3. 初学Selenium遇到的那些坑

    一.遇到一个下拉选择框,可以点击继续选择: 所以click两次就可以了: 二.国际话问题 bdId.selectByIndex(index);//index位下拉框内容的下标,从0开始,  数组形式[ ...

  4. 常用git命令(一)

    git add 命令. 这是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等. 将这个命令理解为“添加内容到下一次提交中”而不是“将一 ...

  5. 基本http服务性能测试(Python vs Golang)

    最近学习Golang,总想体验下并发到底有多叼,必我大 python强势多少. 学习了官方教程的http 服务,用性能测试工具wrk测试了下,发现结果很令人惊讶- wrk可以参考我的博客,有基本用法说 ...

  6. oracle 修改字符集 修改为ZHS16GBK

    oracle数据库的字符集更改 A.oracle server 端 字符集查询 select userenv('language') from dual 其中NLS_CHARACTERSET 为ser ...

  7. Java复习10.Servlet编程

    Java复习10. Servlet编程知识 20131008 前言: 之前在大三下的时候,学习了一个月的JSP和Servlet知识,但是没有什么项目经验,把JSP Web开发学习实录看了前面几张,后面 ...

  8. MVC DateTime 字段 EditTime 必须是日期模板只能用于字段访问、属性访问、一维数组索引或单参数自定义索引器表达式

    ASP.NET MVC 中model含有DateTime类型的字段 更新字段时提示:字段 EditTime必须是日期,. 但是明明填入的是日期还是给出这个提示, 看有的博客说那是因为日期形式错了,如果 ...

  9. jQuery Ajax同步参数导致浏览器假死怎么办

    俗话说不作死就不会死,今天作死了一回,写了一个比较二逼的函数,遇到了同步Ajax引起的UI线程阻塞问题,在此记录一下.   事情起因是这样的,因为页面上有多个相似的异步请求动作,本着提高代码可重用性的 ...

  10. etl工具-Bireme

    前段时间做数据仓库项目,自己实现了一部分etl功能,后面一直没有时间去深入挖掘.这个工具貌似不错,写个帖子做下记录: https://hashdatainc.github.io/bireme/READ ...