.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理解为一种“面向对象的设计模式 ...
随机推荐
- 【算法随记七】巧用SIMD指令实现急速的字节流按位反转算法。
字节按位反转算法,在有些算法加密或者一些特殊的场合有着较为重要的应用,其速度也是一个非常关键的应用,比如一个byte变量a = 3,其二进制表示为00000011,进行按位反转后的结果即为110000 ...
- java xml的读取与写入(dom)
首先,先获取到文档对象 private static Document getDocument(String path) { //1.创建DocumentBuilderFactory对象 Docume ...
- SpringBoot中的五种对静态资源的映射规则
目录 1. webjars:以jar包的方式引入静态资源 2./** 访问当前项目的任何资源 3.首页index.html,被" /** "映射 4.自定义图标 / favico ...
- < python音频库:Windows下pydub安装配置、过程出现的问题及常用API >
< python音频库:Windows下pydub安装配置.过程出现的问题及常用API > 背景 刚从B站上看过倒放挑战之后也想体验下,心血来潮一个晚上完成了基本的实现.其中倒放与播放部分 ...
- 「CH2101」可达性统计 解题报告
CH2101 可达性统计 描述 给定一张N个点M条边的有向无环图,分别统计从每个点出发能够到达的点的数量.N,M≤30000. 输入格式 第一行两个整数N,M,接下来M行每行两个整数x,y,表示从x到 ...
- 小小知识点(三十七)OFDM和OFDMA的区别以及OFDMA与SC-FDMA的区别
OFDM和OFDMA的区别 OFDM(orthogonal frequency division multiplexing),which assigns one block (in time ) to ...
- Cannot access org.springframework.context.ConfigurableApplicationContext
Cannot access org.springframework.context.ConfigurableApplicationContext 需要将有问题的模块 删除 后重新导入 即可 IDEA ...
- 【tf.keras】Linux 非 root 用户安装 CUDA 和 cuDNN
TensorFlow 2.0 for Linux 使用时报错:(cuDNN 版本低了) E tensorflow/stream_executor/cuda/cuda_dnn.cc:319] Loade ...
- 使用gravatar生成头像
avatar代表您在线的图像,当你与网站互动时,你的名字旁边会出现一张图片. Gravatar是一个全球通用的头像.你只需上传一次并创建自己的个人资料,然后当你加入任何支持Gravatar的网站时,你 ...
- java 字典 map 和 list.forEach
1.keySet key 集 2.values value 集(为何不叫valueSet)... 3.entrySet key value 集 List<?> 循环 1.Iterable ...