使用 Task.Wait()?立刻死锁(deadlock)
最近读到一篇异步转同步的文章,发现其中没有考虑到异步转同步过程中发生的死锁问题,所以特地在本文说说异步转同步过程中的死锁问题。
文章作者 林德熙 已经修复了描述:
- win10 uwp 异步转同步
什么情况下会产生死锁?
调用 Task.Wait()
或者 Task.Result
立刻产生死锁的充分条件:
1. 调用 Wait()
或 Result
的代码位于 UI 线程;
1. Task
的实际执行在其他线程。
死锁的原因:
UWP、WPF、Windows Forms 程序的 UI 线程都是单线程的。为了让使用了 async
/await
的代码像使用同步代码一样简单,WPF 程序的 Application
类在构造的时候会将主 UI 线程 Task
的同步上下文设置为 DispatcherSynchronizationContext
的实例,这在我的另一篇文章 Task.Yield 中也有过说明。
当 Task
的任务结束时,会从 AsyncMethodStateMachine
中调用 Awaiter
的 OnComplete()
方法,而 await
后续方法的执行靠的就是 OnComplete()
方法中一层层调用到 DispatcherSynchronizationContext
里的 Post
方法:
/// <summary>
/// Asynchronously invoke the callback in the SynchronizationContext.
/// </summary>
public override void Post(SendOrPostCallback d, Object state)
{
// Call BeginInvoke with the cached priority. Note that BeginInvoke
// preserves the behavior of passing exceptions to
// Dispatcher.UnhandledException unlike InvokeAsync. This is
// desireable because there is no way to await the call to Post, so
// exceptions are hard to observe.
_dispatcher.BeginInvoke(_priority, d, state);
}
这里就是问题的关键!!!
如果 _dispatcher.BeginInvoke(_priority, d, state);
这句代码在后台线程,那么此时 UI 线程处于 Wait()
/Result
调用中的阻塞状态,BeginInvoke
中的任务是无论如何也无法执行到的!于是无论如何都无法完成这个 Post
任务,即无论如何也无法退出此异步任务的执行,于是 Wait()
便无法完成等待……死锁……
这里给出最简复现的例子代码:
DoAsync().Wait();
async Task DoAsync()
{
await Task.Run(() => { });
}
无论是 WPF 还是 UWP,只要在 UI 线程上调用上述代码,必然死锁!
什么情况下不会产生死锁?
阅读了本文一开始说的那篇文章 win10 uwp 异步转同步 后,你一定好奇为什么此文的情况不会产生死锁。
那是因为,它不满足本文提到的充分条件——StorageFolder.GetFolderFromPathAsync("")
和 StorageFolder.GetFolderFromPathAsync("")
这两个方法并不会在后台线程执行!
逗我???不在后台线程执行怎么做到的异步等待!!!
是的,读写文件,访问网络,这些 IO 阻塞的操作执行时,里面根本就没有线程,详情请阅读:There Is No Thread。
还有另一些操作,也没有后台线程的参与,于是也不存在从后台线程回到主线程导致死锁的情况。如 Task.Yield,还有 InvokeAsync,它们也不会造成死锁。
另外,如果是控制台程序,或者一个普通的非 UI 线程,其 SynchronizationContext
为 null,那么异步任务执行完后不需要回到原有线程,也不会造成死锁。
总结不会造成死锁的充分条件:
1. 异步操作执行完后不需要回到原有线程(例如非 UI 线程和控制台线程);
1. 异步操作不需要单独的线程执行任务。
如何避免死锁?
明确了会造成死锁的条件和不会造成死锁的条件后,我们只需要做到以下几点即可避免死锁了:
- 在 UI 线程,如果使用了
async
/await
,就尽量不要再使用Task.Wait()
/Task.Result
了,就一直异步一条路走到黑好了(微软称其为 Async All the Way)。 - 如果可能,尽量在异步任务后添加
.ConfigureAwait(false)
;这样,异步任务后面继续执行的代码就不会回到原 UI 线程了,而是直接从线程池中再取出一个线程执行;这样,即便 UI 线程后续可能有别的原因造成阻塞,也不会产生死锁了。
把原来的代码改成这样,就不会死锁了:
await DoAsync();
async Task DoAsync()
{
await Task.Run(() => { });
}
没错!只能是一路 async
/await
。微软将其描述为:async
/await
会像病毒一样在你的代码中传播。
Others have also noticed the spreading behavior of asynchronous programming and have called it “contagious” or compared it to a zombie virus.
这句话的原文参见:Async/Await - Best Practices in Asynchronous Programming
为了防止真的有代码的调用者使用 Wait()
,我们也得写出防 SB 的代码来:
async Task DoAsync()
{
await Task.Run(() => { }).ConfigureAwait(false);
}
这样,即便真的使用 DoAsync().Wait()
也不会发生死锁。注意,整个方法调用链都需要使用 .ConfigureAwait(false)
才能够防止线程切换时,在调用方的 Wait()
方法中发生死锁。详见我的另一篇博客 在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁。)
参考资料
使用 Task.Wait()?立刻死锁(deadlock)的更多相关文章
- 菜鸡的Java笔记 - java 线程的同步与死锁 (同步 synchronization,死锁 deadlock)
线程的同步与死锁 (同步 synchronization,死锁 deadlock) 多线程的操作方法 1.线程同步的产生与解决 2.死锁的问题 ...
- 又踩.NET Core的坑:在同步方法中调用异步方法Wait时发生死锁(deadlock)
之前在将 Memcached 客户端 EnyimMemcached 迁移 .NET Core 时被这个“坑”坑的刻骨铭心(详见以下链接),当时以为只是在构造函数中调用异步方法(注:这里的异步方法都是指 ...
- iOS学习笔记-死锁deadlock理解
1.首先看一下官方文档的解释,这个block的队列是同步执行的,不像异步,这个方法直到block执行完毕才会返回 2.主线程一旦开启,就要先把自己的代码执行完成之后,才去执行加入到主队列中的任务 De ...
- c# async Task await Result 死锁
最近项目数据量较大,使用 async Task异步增加执行效率 遇到问题,当前有2个计算非常耗时,现在需要你优化一下,这2个计算并行执行,2个计算执行完成后将2个结果sum返回给用户 当前我是这样实现 ...
- 使用默认system_health分析死锁(Deadlock)
在2008之前我们分析死锁须要用profiler trace或者trace flag 1222,1204.在2008中引入了一个新功能:Extended Events(扩展事件).能够监控Deadlo ...
- 一句 Task.Result 就死锁, 这代码还怎么写?
一:背景 1. 讲故事 前些天把 .NET 高级调试 方面的文章索引到 github 的过程中,发现了一个有意思的评论,详见 文章,截图如下: 大概就是说在 Winform 的主线程下执行 Task. ...
- 第9章 用内核对象进行线程同步(4)_死锁(DeadLock)及其他
9.7 线程同步对象速查表 对象 何时处于未触发状态 何时处于触发状态 成功等待的副作用 进程 进程仍在运行的时候 进程终止的时(ExitProcess.TerminateProcess) 没有 线程 ...
- 有关于并发中的死锁(Deadlock)、饥饿(Starvation)、活锁(Livelock)
最近在看<实战Java高并发程序设计>,发现了之前没有接触过的几个名词. 死锁:之前在接触多线程的时候,接触过死锁的情况.死锁是线程中最糟糕的情况,如下面的图中的四辆车子一样,如果没有一辆 ...
- Java 多线程 - 死锁deadlock产生原因+避免方法
ref: java中产生死锁的原因及如何避免 https://blog.csdn.net/m0_38126177/article/details/78587845 java如何避免死锁 http:// ...
随机推荐
- Struts2框架学习第二章——Struts2下的HelloWorld
本章要点 — Struts 2的下载和安装 — 纯手工创建一个Web应用 — 纯手工创建一个Struts 2应用 — 实现Struts 2的Action — 配置Struts 2的Action — ...
- Javascript中的void
原来void是将其后的字面量当元表达式执行,并永远返回undefined.同时undefined不是关键词.. 由于JS表达式偏啰嗦,于是最近便开始采用Coffeescript来减轻负担.举个栗子,当 ...
- OpenCL将数组从内存copy到显存
本来想对上一篇博客做优化,优化效果不明显.但知识点还是要记一下. 初衷是想把上一篇博客中定义域的计算搬到CPU来计算,因为定义域的计算对于每一个kernel都是一样的,所以直接读取应该是可以进一步减小 ...
- 原生javascript-日历插件编写
在线实例:http://lgy.1zwq.com/calendar/ 按照我们常用的日历格式,是7*6的格子,所以生成格子的总数就确定为42 例子:(如:2013年8月,这个时间为例子) /*---- ...
- fastdfs 集群配置
fastdfs 简介FastDFS是一个国产开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储.文件同步.文件访问(文件上传.文件下载)等,解决了大容量存储和负载均衡的问题.特别适合以文 ...
- 禁用iPhone手机浏览器上给电话号码自动加上的link样式(苹果手机自动给手机号加样式)
原文地址:http://blog.csdn.net/atec2000/article/details/44631633 iPhone手机上的浏览器(如Safari),在解析网页的时候会自动给 像是电话 ...
- halocn/C++ (第一篇)
在使用C++编写halcon之前,确定自己有较好的C++基础,并熟悉一套开发平台如VC Programmers_guide.pdf chapter7中有关于creating Aplicat ...
- django 自定义用户表替换系统默认表
首先新建一个users应用,编写这个应用的models类. from django.contrib.auth.models import AbstractUser class UserProfile( ...
- java语言基础-变量
一丶变量的基本概念 1.什么是变量 (1).内存中的一个存储区域 (2).该区域有自己的名称(变量名),和类型(数据类型) (3.)该区域的数据可以在同一类型范围内不断变化(定义变量的主要目的是因为数 ...
- 20165202 2017-2018-2 《Java程序设计》第8周学习总结
20165202 2017-2018-2 <Java程序设计>第8周学习总结 教材学习内容总结 Ch12 进程与线程 线程是比进程更小的单位,一个进程在其执行过程中,可以产生多个线程 Ja ...