1 调用异步方法 GetStringAsync 时,.NET 框架为我们创建了异步任务 T;
2 应用 await 时, 框架捕获当前环境, 存储在 SynchronizationContext 对象并附加于以上 Task;
3 同时, 控制权返回到原上层调用函数, 返回一个未完成的 Task<int > 对象, 这个时候需要关注上层调用函数使用 await 异步等待还是使用 Result/Wait() 方式同步等待
4 异步任务 T 执行完成, await 之后的代码将会成为 continuation block, 默认情况下利用捕获的 SynchronizationContext 对象执行该 continuation block 代码.
内部实际是将 continuation block 代码放入 SynchronizationContext 的 Post 方法.
不同的. NET 框架因各自独特的需求 有不同的 Post 实现 (Post 是一个虚方法):
- 默认的 SynchronizationContext.Post 实现是将 委托通过 QueueUserWorkItem 传递给 ThreadPool, 所以控制台程序也不会出现死锁.
- Windows Form 有 WindowsFormSynchronizationContext, Post 方法将委托传递给 Control.BeginInvoke
- WPF 有 DispatcherSynchronizationContext , Post 方法将委托传递给 Dispatcher.BeginInvoke
3. 引言代码为什么发生 deadlock, 而 ASP.NET Core 为什么不会发生类似 deadlock?
仔细观察引言代码, 控制返回到 上层调用函数时, 该调用函数使用 Result 属性去等待任务结果, Result/Wait() 等同步方式会导致调用线程挂起等待任务完成. 而在异步方法内部, await 触发的异步任务执行完成后, 会尝试利用捕获的同步上下文执行剩余代码, 而该同步上下文中的线程正同步等待整个异步任务完成, 形成死锁.
正因为如此, 我们提出:
- 在原调用函数始终 使用 await 方法, 这样该线程是异步等待 任务完成.
- 在异步任务内部应用 ConfigureAwait(false) 方法:
MSDN ConfigureAwait(): true to attempt to marshal the continuation back to the original context captured; otherwise, false
另外注意: ASP.NET Core 不存在 SynchronizationContext , 故不会发生类似的死锁.
总结:
虽然 await/async 语法糖让我们在编写. NET 异步程序时得心应手, 随心所欲, 但是不要忘记了 SynchronizationContext 在其中转承起合的作用.
利用能够保存当前执行代码的上下文特性, SynchronizationContext 在线程切换后帮我们有能力执行各种骚操作.
来源: https://www.cnblogs.com/mi12205599/p/10644071.html