await后无代码是否仍生成延续任务?为何会触发上下文死锁?
1. 什么是「async方法的剩余部分」
哪怕你在await之后没有写任何业务代码,编译器为async方法自动生成的状态机逻辑里,await之后仍然有必须执行的收尾步骤,这部分就是描述中提到的「剩余部分」:
- 将
async方法关联的返回值Task从「运行中」状态更新为「已完成」状态 - 处理异步执行过程中抛出的异常,将异常包装到返回的
Task中
这些逻辑是async方法的固有生成逻辑,和你有没有手动写后续业务代码无关。
2. 没有后续业务代码为什么还会发生死锁
我们可以完整还原GUI/旧ASP.NET单线程上下文下的执行流程:
- 线程A调用
Test()方法,占用当前唯一的SynchronizationContext(这类上下文同一时间仅允许执行一段代码) - 执行
DelayAsync()到await Task.Delay(1000)时,Task.Delay未完成,因此DelayAsync返回一个未完成的Task,同时捕获当前的SynchronizationContext,约定Task.Delay完成后回到该上下文执行上述的收尾逻辑 - 回到
Test()方法执行delayTask.Wait(),同步阻塞线程A,线程A会一直卡在这步等待delayTask变为已完成状态 - 1秒后
Task.Delay运行完成,此时需要在之前捕获的上下文里执行收尾逻辑,但上下文正被卡在Wait()的线程A占用,收尾逻辑只能排队等待上下文释放 - 此时形成循环等待:线程A等待
delayTask完成,delayTask等待收尾逻辑执行完才会标记为完成,收尾逻辑等待线程A释放上下文,死锁就发生了。
如果要避免这个死锁,只需要把await Task.Delay(1000)修改为await Task.Delay(1000).ConfigureAwait(false),相当于告知异步状态机不需要捕获原上下文,Task.Delay完成后直接在线程池线程执行收尾逻辑,不需要等待原上下文释放,就可以打破循环等待。
内容的提问来源于stack exchange,提问作者David Klempfner
相关产品推荐
相关产品推荐

