await Task传异步Lambda问题
微软在.NET4.5中升级了C#语言到5.0,加入了await和async语法,极大地方便了广大开发人员的异步编程,也是为了和WinRT API配套,因为这套API充满了异步编程。
在开发过程中发现有时await不住?!流程还是往下走,觉得可能是使用有问题,于是进行了一下研究,发现了原因。
看下面的一组代码的运行结果及分析说明,WPF平台,代码在按钮的Click事件中。
1.
Debug. WriteLine( "1: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
Task. Factory. StartNew( async () =>
{
Debug. WriteLine( "2: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
await Task. Delay(5000);
Debug. WriteLine( "3: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
});
Debug. WriteLine( "4: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
点一下按钮等运行完再点一下按钮的运行结果为:
1: 10 03:24:03.478
4: 10 03:24:03.484
2: 6 03:24:03.508
3: 12 03:24:08.509
1: 10 03:24:12.721
2: 6 03:24:12.722
4: 10 03:24:12.722
3: 12 03:24:17.744
连点两下按钮的运行结果为:
1: 10 03:29:50.103
2: 16 03:29:50.104
4: 10 03:29:50.104
1: 10 03:29:50.265
4: 10 03:29:50.266
2: 16 03:29:50.266
3: 17 03:29:55.125
3: 16 03:29:55.289
分析:Task.Factory.StartNew,在不加await的情况下,如果传人异步的Lambda,可能先执行Task中的逻辑,也可能先执行后面的逻辑,不能确定,所以这种写法不对。
2.
Debug. WriteLine( "1: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
await Task. Factory. StartNew( async () =>
{
Debug. WriteLine( "2: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
await Task. Delay(5000);
Debug. WriteLine( "3: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
});
Debug. WriteLine( "4: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
点一下按钮等运行完再点一下按钮的运行结果为:
1: 10 03:25:16.212
2: 14 03:25:16.215
4: 10 03:25:16.217
3: 14 03:25:21.217
1: 10 03:25:44.692
2: 15 03:25:44.694
4: 10 03:25:44.695
3: 16 03:25:49.696
连点两下按钮的运行结果为:
1: 10 03:31:13.661
2: 15 03:31:13.662
4: 10 03:31:13.663
1: 10 03:31:13.826
2: 15 03:31:13.827
4: 10 03:31:13.828
3: 15 03:31:18.663
3: 14 03:31:18.837
分析:Task.Factory.StartNew,加await的情况下,如果传人异步的Lambda,可以保证先执行Task中的逻辑,再执行后面的逻辑。等等!Task中的逻辑没有执行完,只执行了一部分,如果我们的目的是等Task中的逻辑执行完再执行后面的逻辑,这种写法也是错误的,会产生BUG。
那正确的写法是什么呢,在VS中把鼠标移到await Task. Factory. StartNew( async () =>的StartNew方法上,可以看到返回值是Task<Task>。Task<Task>是什么意思呢,意思是返回一个返回Task的Task。对Task<Task>进行await会得到Task,要想等待这个Task运行完,还得await。
3.
Debug. WriteLine( "1: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
await await Task. Factory. StartNew( async () =>
{
Debug. WriteLine( "2: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
await Task. Delay(5000);
Debug. WriteLine( "3: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
});
Debug. WriteLine( "4: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
点一下按钮等运行完再点一下按钮的运行结果为:
1: 10 03:33:27.212
2: 12 03:33:27.213
3: 7 03:33:32.214
4: 10 03:33:32.214
1: 10 03:33:35.142
2: 12 03:33:35.143
3: 12 03:33:40.145
4: 10 03:33:40.146
连点两下按钮的运行结果为:
1: 10 03:34:57.375
2: 3 03:34:57.376
1: 10 03:34:57.556
2: 3 03:34:57.557
3: 3 03:35:02.377
4: 10 03:35:02.378
3: 3 03:35:02.557
4: 10 03:35:02.559
分析:果然加两个await会得到想要的效果。Task.Factory.StartNew,加await await的情况下,如果传人异步的Lambda,可以保证先执行Task中的全部逻辑,再执行后面的逻辑。
再来看其他情况。
4.
Debug. WriteLine( "1: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
Task. Factory. StartNew(() =>
{
Debug. WriteLine( "2: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
Task. Delay(5000). Wait();
Debug. WriteLine( "3: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
});
Debug. WriteLine( "4: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
点一下按钮等运行完再点一下按钮的运行结果为:
1: 10 03:53:56.809
2: 14 03:53:56.809
4: 10 03:53:56.809
3: 14 03:54:01.831
1: 10 03:54:03.441
2: 3 03:54:03.441
4: 10 03:54:03.441
3: 3 03:54:08.463
连点两下按钮的运行结果为:
1: 10 03:54:42.533
4: 10 03:54:42.533
2: 12 03:54:42.535
1: 10 03:54:42.707
4: 10 03:54:42.708
2: 7 03:54:42.708
3: 12 03:54:47.536
3: 7 03:54:47.731
分析:Task.Factory.StartNew,不加await的情况下,如果传人普通的Lambda,也会出现先执行Task中的部分逻辑,就去执行后面的逻辑的情况。
在VS中把鼠标移到Task. Factory. StartNew(() =>的StartNew方法上,看到返回的是Task,Task需要await,没加await是不对的。
5.
Debug. WriteLine( "1: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
await Task. Factory. StartNew(() =>
{
Debug. WriteLine( "2: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
Task. Delay(5000). Wait();
Debug. WriteLine( "3: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
});
Debug. WriteLine( "4: " + Thread .CurrentThread .ManagedThreadId + " " + DT(DateTime .Now ));
点一下按钮等运行完再点一下按钮的运行结果为:
1: 10 03:55:48.141
2: 16 03:55:48.143
3: 16 03:55:53.144
4: 10 03:55:53.145
1: 10 03:55:56.560
2: 16 03:55:56.561
3: 16 03:56:01.563
4: 10 03:56:01.564
连点两下按钮的运行结果为:
1: 10 04:01:34.54
2: 16 04:01:34.54
1: 10 04:01:34.218
2: 15 04:01:34.219
3: 16 04:01:39.56
4: 10 04:01:39.57
3: 15 04:01:39.227
4: 10 04:01:39.227
分析:Task.Factory.StartNew,加await的情况下,如果传人普通的Lambda,能保证先执行Task中的全部逻辑,然后再执行后面的逻辑。
能不能加两个await,答案是不能,那样不能通过编译。如果强行把Lambda改为异步Lambda,可以编译通过也能得到正确的结果,但Resharper会给出警告,这样做没有必要,也不合适。
结论:使用Task在背后线程中执行耗时逻辑以避免阻塞UI线程是合适的做法。传入一个Lambda可以方便地实现逻辑,增加代码的可读性。如果Lambda中全是同步逻辑,没有使用await,即为普通Lambda,这时对Task的执行使用await不会有问题。但如果Lambda中需要使用异步API或调用异步方法,就必须改为异步Lambda,这时对Task的执行必须加两个await,才能达到想要的效果,先执行Task中的全部逻辑,然后再执行后面的逻辑。
await Task传异步Lambda问题的更多相关文章
- await Task.Yield()和await Task.CompletedTask有什么不同
有时候我们在代码中要执行一些非常耗时的操作,我们不希望这些操作阻塞调用线程(主线程)的执行,因为调用线程(主线程)可能还有更重要的工作要做,我们希望将这些非常耗时的操作由另外一个线程去执行,这个时候就 ...
- C#扫盲篇(四):.NET Core 的异步编程-只讲干货(async,await,Task)
关于async,await,task的用法和解释这里就不要说明了,网上一查一大堆.至于为啥还要写这篇文章,主要是其他文章水分太多,不适合新手学习和理解.以下内容纯属个人理解,如果有误,请高手指正.本文 ...
- 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法
什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...
- 实践基于Task的异步模式
Await 返回该系列目录<基于Task的异步模式--全面介绍> 在API级别,实现没有阻塞的等待的方法是提供callback(回调函数).对于Tasks来说,这是通过像ContinueW ...
- async/await Task Timeout
async/await Task Timeout 在日常的电脑使用过程中,估计最难以忍受的就是软件界面"卡住""无响应",在我有限的开发生涯中一直都是在挑战 它 ...
- 实现基于Task的异步模式
返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的方法都是异步方法,编 ...
- 基于Task的异步模式的定义
返回该系列目录<基于Task的异步模式--全面介绍> 命名,参数和返回类型 在TAP(Task-based Asynchronous Pattern)中的异步操作的启动和完成是通过一个单独 ...
- Task的异步模式
Task的异步模式 返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的 ...
- async和await关键字实现异步编程
async和await关键字实现异步编程 异步编程 概念 异步编程核心为异步操作,该操作一旦启动将在一段时间内完成.所谓异步,关键是实现了两点:(1)正在执行的此操作,不会阻塞原来的线程(2)一旦 ...
随机推荐
- 前端调试利器---nproxy
前言:习惯了在windows环境中使用Fiddler的童鞋们,是不是感觉它的网络重定向功能很酷,Fiddler能按照你设置的规制捕获网络请求,再指向本地文件,如拦截你的js文件到本地,就能很快的调试线 ...
- 使用ssh client与bash scripts轻松管理多台主机
当我们需要控制一个局域网中的很多台服务器时,一个简单的全局操作可能会被放大地异常繁琐,这时我们就会需要新的工具来快速完成这种工作. 我们将使用ssh客户端提供的一些工具来快速完成这一开发工作,我们的开 ...
- Unresolved external 'AlphaBlend' referenced from
AlphaBlend [Linker Error] Unresolved external 'AlphaBlend' referenced from 解决方案 把文件msimg32.lib添加到工程中 ...
- UVALIVE 4556 The Next Permutation
4556 The Next PermutationFor this problem, you will write a program that takes a (possibly long) str ...
- javax.persistence.TransactionRequiredException: No transactional EntityManager available
在操作中加上@Transcational注解,一般是用于修改或者删除操作.
- Linux 入门知识一(附上如何解决Ubuntu的root密码问题)
.centos有拥有七个控制台,其中第一到第六个是字符界面,第七个是图形界面 切换的快捷键是ctrl+shift+fn(n为自然数) 输入tty的话,可以检查当前处于哪个控制台 如何在cent ...
- 使用net.sf.fjep.fatjar插件将第三方JAR包打包进自已的JAR包中
一般单个工程,在没有应用别人的jar包时导出为jar很简单,只要设置一个Main-Class就行了,也就是选择程序入口(main所在类).但是涉及到了数据库或需要用到第三方的JAR,就需要用到相应的数 ...
- 201671010140. 2016-2017-2 《Java程序设计》java学习第十周
---恢复内容开始--- Java学习第十周 本周,学习泛型程序设计,泛型也被称为参数化类型(parameterized type),就是在定义类.接口和方法时,通过类型参数指示将要处理的 ...
- EMC存储同时分配空间到两台服务器路径不一致-双机盘符不一致
以下方式将i盘盘符换成g盘,g盘盘符换成i emcpadm rename -s emcpoweri -t emcpowerj emcpadm rename -s emcpowerg -t emcpow ...
- 手把手教你生成二维码-google.zxing
一.目标 输入网址,生成网址的二维码 二.概况 1.效果:UI丑,但功能实现了 2.项目目录 三.用到的第三方资源 1.google的扫码包zxing 2.JQuery 四.步骤(用myEclipse ...