原则上我们应该避免编写混合同步和异步的代码,这其中最大的问题就是很容易出现死锁。让我们来看下面的例子:

        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)——谨慎混合同步和异步代码的更多相关文章

  1. Java多线程学习笔记(三)同步和异步

    首先是一段代码: public class HasSelfPrivateNum { public void addI(String username){ try { int num=0; if(use ...

  2. .NET CORE学习笔记系列(2)——依赖注入【1】控制反转IOC

    原文:https://www.cnblogs.com/artech/p/net-core-di-01.html 一.流程控制的反转 IoC的全名Inverse of Control,翻译成中文就是“控 ...

  3. .NET Core学习笔记(7)——Exception最佳实践

    1.为什么不要给每个方法都写try catch 为每个方法都编写try catch是错误的做法,理由如下: a.重复嵌套的try catch是无用的,多余的. 这一点非常容易理解,下面的示例代码中,O ...

  4. .NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]

    原文https://www.cnblogs.com/artech/p/net-core-di-07.html 包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IS ...

  5. .NET CORE学习笔记系列(2)——依赖注入[6]: .NET Core DI框架[编程体验]

    原文https://www.cnblogs.com/artech/p/net-core-di-06.html 毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动 ...

  6. .NET CORE学习笔记系列(2)——依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...

  7. .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  8. .NET CORE学习笔记系列(2)——依赖注入【3】依赖注入模式

    原文:https://www.cnblogs.com/artech/p/net-core-di-03.html IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架中以实现对流 ...

  9. .NET CORE学习笔记系列(2)——依赖注入【2】基于IoC的设计模式

    原文:https://www.cnblogs.com/artech/p/net-core-di-02.html 正如我们在<控制反转>提到过的,很多人将IoC理解为一种“面向对象的设计模式 ...

随机推荐

  1. 百度DMA+小度App的蓝牙语音解决方案入局

    前记   人机交互经历了三个阶段键鼠.触屏和语音交互.在国外,谷歌.亚马逊.苹果等巨头的竞争已经到达白热化状态:在国内,百度的DuerOS凭借着入局早,投入大,已经成为国内语音互交的一面旗帜.无论是从 ...

  2. [梁山好汉说IT] 梁山好汉和秒杀系统

    [梁山好汉说IT] 梁山好汉和秒杀系统 0x00 摘要 今天看了一篇好文章,里面一些思路颇值得借鉴.先摘录总结精华.然后看看梁山好汉如何处理秒杀系统(系统隔离/系统搭建/风控过滤/削峰/信号广播... ...

  3. apium环境搭建(mac)

    appium 环境搭建 安装homebrew(Mac OSX上的软件包管理工具) $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubuse ...

  4. Java动态代理 ----- jdk代理与cglib代理

    1.jdk代理 针对接口进行代理,接口可以没有方法, InvocationHandler会拦截所有方法,不过好像意义不大....只能执行Object类的方法,执行结果有点奇怪... package t ...

  5. C++版本的UnEscape 解析\uxxxx\uxxxx编码字符

    解析类似于这种Unicode编码格式的字符串 \u5b55\u5987\u88c5\u590f\u88c52018\u65b0\u6b3e\u5bbd\u677e\u77ed\u8896\u4e2d\ ...

  6. $Poj3208$ 启示录 数位统计$DP$

    Poj  AcWing Description Sol  这题长得就比较像数位$DP$叭. 所以先用$DP$进行预处理,再基于拼凑思想,通过"试填法"求出最终的答案. 设$F[i] ...

  7. Ecshop在模板中判断用户是否登陆,获取用户等级信息

    ecshop模板中smarty怎样判断用户等级.用户id.用户昵称用户名,请看以下方法,使用全局变量 <!-- {if $smarty.session.user_rank gt 1}--> ...

  8. shiro整合springmvc

    说明   代码及部分相关资料根据慕课网Mark老师的视频进行整理   其他资料: shiro官网 流程 配置 1) 配置web.xml整合shiro 把shiro整合到springMVC实质上是在we ...

  9. 22.Python安装和卸载第三方模块方法

    安装和卸载第三方开源模块的步骤:下例,安装urllib3模块的步骤. 1.安装开源模块步骤: 按键盘windows键+r键,输出cmd回车.或开始->windows系统->命令提示符: 输 ...

  10. 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 ...