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中的全部逻辑,然后再执行后面的逻辑。

时间: 2024-10-15 08:26:21

await Task传异步Lambda问题的相关文章

实践基于Task的异步模式

Await 返回该系列目录<基于Task的异步模式--全面介绍> 在API级别,实现没有阻塞的等待的方法是提供callback(回调函数).对于Tasks来说,这是通过像ContinueWith的方法实现的.基于语言的异步支持通过允许在正常控制流内部等待异步操作隐藏callbacks,具有和编译器生成的代码相同的API级别的支持. 在.Net 4.5,C#直接异步地支持等待的Task和Task<TResult>,在C#中使用"await"关键字.如果等待一个Ta

异步Lambda表达式问题的探索

"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 异步Lambda表达式问题的探索 - 飞鸿踏雪 - 博客频道 - CSDN.NET 飞鸿踏雪 如果可以,为什么不让一切更好一点呢 目录视图 摘要视图 订阅 [活动]2017 CSDN博客专栏评选 &nbsp [5月书讯]流畅的Python,终于等到你!    &

实现基于Task的异步模式

返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的方法都是异步方法,编译器会使用TAP执行必要的转换从而异步地实现方法.这样的方法应该返回Task或者Task<TResult>类型.在后者的案例中,方法体应该返回一个TResult,且编译器将确保通过返回的Task<TResult>是可利用的.相似地,方法体内未经处理的异常会被封送到输出的task,造成返

C#的多线程——使用async和await来完成异步编程(Asynchronous Programming with async and await)

https://msdn.microsoft.com/zh-cn/library/mt674882.aspx 侵删 更新于:2015年6月20日 欲获得最新的Visual Studio 2017 RC文档,参考Visual Studio 2017 RC Documentation. 使用异步编程,你可以避免性能瓶颈和提升总体相应效率.然而,传统的异步方法代码的编写方式比较复杂,导致它很难编写,调试和维护. Visual Studio 2012引入了一个简单的异步编程的方法,依赖.NET Fram

Task的异步模式

Task的异步模式 返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的方法都是异步方法,编译器会使用TAP执行必要的转换从而异步地实现方法.这样的方法应该返回Task或者Task<TResult>类型.在后者的案例中,方法体应该返回一个TResult,且编译器将确保通过返回的Task<TResult>是可利用的.相似地,方法体内未经处理的异常会被封送到输

async和await关键字实现异步编程

async和await关键字实现异步编程 异步编程 概念 异步编程核心为异步操作,该操作一旦启动将在一段时间内完成.所谓异步,关键是实现了两点:(1)正在执行的此操作,不会阻塞原来的线程(2)一旦启动的此操作,可以继续执行其他任务.当该操作完成时,将调用回调函数来通知该操作已经结束. [注]:本人一直以为同步和异步都属于多线程的范畴,到今天才明白完全错误,异步和多线程是属于不同范畴,多线程和异步是并发的两种形式,并行处理和线程同步是多线程的两种形式,这是我当前的理解,不知是否有误,文中若有错误,

【5min+】帮我排个队,谢谢。await Task.Yield()

系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net知识等等. 5min+不是超过5分钟的意思,"+"是知识的增加.so,它是让您花费5分钟以下的时间来提升您的知识储备量. 正文 如果您现在正在使用.NetCore的话,相信您对await 和 async这两个关键字再熟悉不过了.它们是为异步编程提供的语法糖,便于我们在代码中更便捷的进行异步

wcf使用task实现异步调用

private async void btnGetEmployees_Click(object sender, RoutedEventArgs e) { txtInfo.Text = "Data is Not Received Yet...."; MyRef.ServiceClient Proxy = new MyRef.ServiceClient(); var Result = await Proxy.GetEmployeesAsync(); dgEmp.ItemsSource =

使用任务Task 简化异步编程

使用任务简化异步编程 Igor Ostrovsky 下载代码示例 异步编程是实现与程序其余部分并发运行的较大开销操作的一组技术. 常出现异步编程的一个领域是有图形化 UI 的程序环境:当开销较大的操作完成时,冻结 UI 通常是不可接受的. 此外,异步操作对于需要并发处理多个客户端请求的服务器应用程序来说非常重要. 在实践过程中出现的异步操作的典型例子包括向服务器发送请求并等待响应.从硬盘读取数据以及运行拼写检查等开销较大的计算. 以一个含 UI 的应用程序为例. 该应用程序可以使用 Window