.NET Core学习笔记(4)——谨慎混合同步和异步代码
原则上我们应该避免编写混合同步和异步的代码,这其中最大的问题就是很容易出现死锁。让我们来看下面的例子:
private void ButtonDelayBlock_Click(object sender, RoutedEventArgs e)
{
Delay100msAsync().Wait();
this.buttonDelayBlock.Content = "Done";
} private async Task Delay100msAsync()
{
await Task.Delay();
}
这段代码取自Sample代码中的AsyncBlockSample工程,一个简单的WPF程序(.NET Core)。
https://github.com/manupstairs/AsyncAwaitPractice
在buttonDelayBlock按钮被点击后,会执行Dealy100msAsync方法,同时我们希望将该异步方法以同步的方式运行。在该方法完成后,将buttonDelayBlock按钮的文字设置为“Done”。
所以我们没有对Delay100msAsync方法应用await关键字,而是通过Wait方法同步等待执行结果。
非常遗憾在WPF之类的GUI程序中,我们点击buttonDelayBlock按钮后,程序将会进入死锁的状态。
这是因为一个未完成的Task被await时(Task.Delay(100)返回的Task),将捕获当前的context,用于Task完成时恢复执行接下来的操作。在GUI程序中,此时的context是当前 SynchronizationContext,而GUI程序中的 SynchronizationContext同一时间只能运行一个线程(在Sample里是UI线程)。
所以当Task.Delay(100)完成时,希望能够回到UI线程接着执行,但UI线程正通过Delay100msAsync.Wait()方法在等待Task完成。这踏马就跟吵架了都在等对方先低头,整个程序都不好了,然后就死了……
值得一提的是Console程序并不会出现上述死锁,这是因为Console程序中的SynchronizationContext可以通过ThreadPool来调度不同线程来完成Task,而不会像GUI程序卡在UI线程进退不得。这样不同的迷惑行为,即使是十分年长的猿类也瑟瑟发抖……

最理想的情况就是只编写异步代码。问题是除非编写UWP这样,从底层API调用就强制异步。不然很难避免旧有的同步API的使用。
更不用说成千上万的旧有代码的维护,迁移桌面程序到MS Store,已有GUI程序Win10 style化需求等等。混合同步和异步代码实在是难以避免的。像例子中需要等待异步方法完成,再根据结果执行的情况就更常见了。
解决上述死锁的一个方式是通过ConfigureAwait方法来配置context。
async Task MyMethodAsync()
{
// Code here runs in the original context.
await Task.Delay();
// Code here runs in the original context.
await Task.Delay().ConfigureAwait(continueOnCapturedContext: false);
// Code here runs without the original
// context (in this case, on the thread pool).
}
如注释所描述,第一个await Task.Delay方法前后的代码块会在相同的context中执行,因为Task完成后仍会返回原先的context。而第二个await Task.Delay则不再依赖原先的context。如果是在GUI程序中执行上面的代码,后续的代码将在ThreadPool,而不是之前的UI线程上执行。
在这种情况下如果出现了对UI元素的操作,便会出现祖传的跨线程操作Exception。
我们回到死锁的问题上,通过ConfigureAwait配置context的代码如下:
private async Task Delay100msWithoutContextAsync()
{
await Task.Delay().ConfigureAwait(false);
}
private void ButtonDelay_Click(object sender, RoutedEventArgs e)
{
Delay100msWithoutContextAsync().Wait();
this.buttonDelay.Content = "Done";
}
我们可以通过这种方式终结异步代码链的传递,将一小块的异步代码隐匿在旧有的同步代码中使用,当然仍需要十分小心。
这里还有一种略显繁琐且奇怪的方式来解决死锁问题:
private void ButtonDelay2_Click(object sender, RoutedEventArgs e)
{
var text = buttonDelay2.Content.ToString();
var length = Task.Run(async () => { return await GetLengthAsync(text); }).Result;
buttonDelay2.Content = $"Total length is {length}";
} private async Task<int> GetLengthAsync(string text)
{
await Task.Delay();
return text.Length;
}
异步方法GetLengthAsync能返回传入字符串的长度,Task.Run(…)会通过ThreadPool来异步地执行一个Func<Task<int>>,且返回Task<int>,而Task<int>.Result属性又以同步的方式阻塞在这里等待结果。
与之前Wait最大的不同,是因为Task.Run利用了ThreadPool没有导致UI线程的死锁。
我们再回到通过ConfigureAwait配置context,等待异步方法结果的方式:
private void ButtonDelay3_Click(object sender, RoutedEventArgs e)
{
var text = buttonDelay3.Content.ToString();
var length = GetLengthWithoutContextAsync(text).Result;
buttonDelay3.Content = $"Button 3 total length is {length}";
} private async Task<int> GetLengthWithoutContextAsync(string text)
{
await Task.Delay().ConfigureAwait(false);
//Cannot access UI thead here, will throw exception
//buttonDelay3.Content = $"Try to access UI thread";
return text.Length;
}
同样是等待Task<type>的Result,相对而言更推荐这种方式,结构清晰且更好理解。注释提到ConfigureAwait(false)之后的代码是不能访问UI线程的。
本篇讨论了混合同步和异步代码时的一些注意事项,还请各位大佬斧正。
Github:
https://github.com/manupstairs/AsyncAwaitPractice

.NET Core学习笔记(4)——谨慎混合同步和异步代码的更多相关文章
- Java多线程学习笔记(三)同步和异步
首先是一段代码: public class HasSelfPrivateNum { public void addI(String username){ try { int num=0; if(use ...
- .NET CORE学习笔记系列(2)——依赖注入【1】控制反转IOC
原文:https://www.cnblogs.com/artech/p/net-core-di-01.html 一.流程控制的反转 IoC的全名Inverse of Control,翻译成中文就是“控 ...
- .NET Core学习笔记(7)——Exception最佳实践
1.为什么不要给每个方法都写try catch 为每个方法都编写try catch是错误的做法,理由如下: a.重复嵌套的try catch是无用的,多余的. 这一点非常容易理解,下面的示例代码中,O ...
- .NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]
原文https://www.cnblogs.com/artech/p/net-core-di-07.html 包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IS ...
- .NET CORE学习笔记系列(2)——依赖注入[6]: .NET Core DI框架[编程体验]
原文https://www.cnblogs.com/artech/p/net-core-di-06.html 毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动 ...
- .NET CORE学习笔记系列(2)——依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...
- .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]
原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...
- .NET CORE学习笔记系列(2)——依赖注入【3】依赖注入模式
原文:https://www.cnblogs.com/artech/p/net-core-di-03.html IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架中以实现对流 ...
- .NET CORE学习笔记系列(2)——依赖注入【2】基于IoC的设计模式
原文:https://www.cnblogs.com/artech/p/net-core-di-02.html 正如我们在<控制反转>提到过的,很多人将IoC理解为一种“面向对象的设计模式 ...
随机推荐
- 百度DMA+小度App的蓝牙语音解决方案入局
前记 人机交互经历了三个阶段键鼠.触屏和语音交互.在国外,谷歌.亚马逊.苹果等巨头的竞争已经到达白热化状态:在国内,百度的DuerOS凭借着入局早,投入大,已经成为国内语音互交的一面旗帜.无论是从 ...
- [梁山好汉说IT] 梁山好汉和秒杀系统
[梁山好汉说IT] 梁山好汉和秒杀系统 0x00 摘要 今天看了一篇好文章,里面一些思路颇值得借鉴.先摘录总结精华.然后看看梁山好汉如何处理秒杀系统(系统隔离/系统搭建/风控过滤/削峰/信号广播... ...
- apium环境搭建(mac)
appium 环境搭建 安装homebrew(Mac OSX上的软件包管理工具) $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubuse ...
- Java动态代理 ----- jdk代理与cglib代理
1.jdk代理 针对接口进行代理,接口可以没有方法, InvocationHandler会拦截所有方法,不过好像意义不大....只能执行Object类的方法,执行结果有点奇怪... package t ...
- C++版本的UnEscape 解析\uxxxx\uxxxx编码字符
解析类似于这种Unicode编码格式的字符串 \u5b55\u5987\u88c5\u590f\u88c52018\u65b0\u6b3e\u5bbd\u677e\u77ed\u8896\u4e2d\ ...
- $Poj3208$ 启示录 数位统计$DP$
Poj AcWing Description Sol 这题长得就比较像数位$DP$叭. 所以先用$DP$进行预处理,再基于拼凑思想,通过"试填法"求出最终的答案. 设$F[i] ...
- Ecshop在模板中判断用户是否登陆,获取用户等级信息
ecshop模板中smarty怎样判断用户等级.用户id.用户昵称用户名,请看以下方法,使用全局变量 <!-- {if $smarty.session.user_rank gt 1}--> ...
- shiro整合springmvc
说明 代码及部分相关资料根据慕课网Mark老师的视频进行整理 其他资料: shiro官网 流程 配置 1) 配置web.xml整合shiro 把shiro整合到springMVC实质上是在we ...
- 22.Python安装和卸载第三方模块方法
安装和卸载第三方开源模块的步骤:下例,安装urllib3模块的步骤. 1.安装开源模块步骤: 按键盘windows键+r键,输出cmd回车.或开始->windows系统->命令提示符: 输 ...
- schedule of 2016-10-17~2016-10-23(Monday~Sunday)——1st semester of 2nd Grade
most important things to do 1.joint phd preparations 2.journal paper to write 3.solid fundamental kn ...