对于 C# 中 Task 的 StartNew 与 WhenAll 相互配合的实验

文章通过实验探讨了C#中Task.Factory.StartNew与Task.WhenAll的配合使用,特别是当StartNew创建的Task执行异步方法时,需要Unwrap()来确保WhenAll能正确等待所有任务完成。实验结果显示,StartNew配合异步方法需使用Unwrap,而Task.Run无需额外处理。
摘要由CSDN通过智能技术生成

对于 C# 中 Task 的 StartNew 与 WhenAll 相互配合的实验

独立观察员 2023 年 3 月 28 日

eba89c9810adca0ea9063edce27a1427.png

一、起因

最近写了一段需要等待几个任务(Task)执行完毕的代码,其中任务是使用 Task.Factory.StartNew 的形式。为什么不用 Task.Run 呢?因为这些任务可能耗时较长,由于 Task 默认是基于线程池的,为了避免耗时较长的任务挤占了软件中其它任务的生存空间,所以采用了给 StartNew 方法传参 TaskCreationOptions.LongRunning,让它单独起一个线程而不是从线程池中取,形如下图:

37adef6a129c0fa4667cf523e2ae07c9.png

然后等待是使用的 Task.WhenAll,本来是打算使用 Task.WaitAll 的,因为要用到异步,所以换用了前者,如上图所示。

二、疑惑

由于 WhenAll 之前没用过,所以在网上搜索一下,看看是否有用得不对的地方。看到了这篇文章《C# Task 使用 WhenAll 和 WaitAll 需要注意的坑》,主要提到了两个坑:

1. 必须添加超时时间,防止无限等待。 2. 等待的 Task 一定要保证是启动的。

第一点还是比较好理解的,看看第二点是怎么说的吧:

89802154dcdb178068bc20c83fc7593c.png

按照作者的说法:

Task.Factory.StartNew 和 Task.Run 区别之一就有 Task.Run 会自动执行 Unwrap 操作

也就是说 Task.Factory.StartNew 比 Task.Run 少了自动解包,所以我们需要自己加上 Unwrap () 方法。

作者应该是个大佬,说的应该都是有道理的,但是有一个疑惑点在这篇文章中没有找到解答:看作者的 StartNew 中的方法都是异步的,也就是有 async 标志,那么问题就来了,如果是同步的方法,也就是没有 async 的情况,还需不需要 Unwrap 呢?

猜测是不需要的,因为同步方法的情况,应该是没有被 “包装” 的。不过作者没有提到这方面,为了严谨起见,决定自己写个测试程序实验一下。

三、实验

在之前做的 Task 测试程序中添加本次需要测试的部分:

5eb58f379f8ed3112817fe4b07548c29.png

如图所示,本次将要演示五种情况,每种情况最后都是用 await Task.WhenAll 进行等待,就不赘述了,直接看不同之处:

1、单纯的 Task.Factory.StartNew 方法(后面简称为 StartNew 方法);

2、StartNew 方法中启动异步方法(带 async,后同);

3、StartNew 方法中启动异步方法,在 StartNew 方法后带上 Unwrap () 方法的调用;

4、单纯的 Task.Run 方法;

5、Task.Run 方法中启动异步方法。

有人可能就要问了,Task.Run 组怎么少了个调用 Unwrap () 方法的情况呢?不是我漏了,也不是我不想加,而是加上的话 VS 就提示错误了,毕竟那样的话应该就属于过度解包了,所以加不得。

先来看看 StartNew 组:

50ca1eb916a7f850c2bb3296ef0b9bb5.png

再来看看 Task.Run 组:

79aa4ba01023046851d1814e59642fb0.png

接下来就是沙场秋点兵了:

3a7465bc4e025b5e216173cf34d77f68.png

结果很明显了,除了 2 号情况(StartNew async WhenAll)异常,其它情况都是正常的(也就是 await Task.WhenAll 能起到等待所有任务结束的作用)。

然后 3 号情况,就是在 2 号情况的基础上加上了 Unwrap 方法进行解包,就轻松挽回了败局,值得称道,给个特写:

a5c5de2927cda6b27b25840bd69ebc00.png

四、结论

通过实验程序,可以得出如下结论:

1、单纯的 Task.Factory.StartNew 方法(内部启动同步方法的情况),以及任意的 Task.Run 方法(无论内部是同步方法还是异步方法),配合 await Task.WhenAll 都能达到预期效果。

2、Task.Factory.StartNew 方法中启动的是异步方法时,配合 await Task.WhenAll 方法达不到等待所有任务完成的效果;如果需要达到预期效果,需要在最后加上 Unwrap 方法。

另外,发现 Task.CurrentId 在这种情况的异步方法中无法获取值,Thread.CurrentThread.ManagedThreadId 则发挥稳定。

至于其它的组合情况以及其它发现,大家可以自行探索尝试。

五、资源

本文测试程序源码地址:https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20230328

另外,说个题外话,本人自用的 WPF 类库 WPFTemplateLib 已打包发布到 NuGet 库中,欢迎大家安装使用:

https://www.nuget.org/packages/WPFTemplateLib/

7b660d66aca82c6978e57b47d200ac18.png

感谢阅读。

原创文章,转载请注明: 转载自 独立观察员 (dlgcy.com)

本文链接地址:[对于 C# 中 Task 的 StartNew 与 WhenAll 相互配合的实验](http://dlgcy.com/csharp-task-startnew-whenall-unwrap/)

C#

【问题】为什么 System.Timers.Timer 更改间隔时间后的第一次触发时间是设定时间的三倍?

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑

【分享】C# 字节帮助类 ByteHelper

C# 在自定义的控制台输出重定向类中整合调用方信息

C# 枚举转列表

.NET

Windows 服务 同时启动多个服务

PostSharp 中 AOP 功能的简单使用

.NET SDK-Style 项目(Core、Standard、.NET5)中的版本号

将 .NET Framework 项目转换为 .NET Standard 项目

ASP.NET Core MVC 网站学习笔记

Unity 容器简单使用方法

Unity容器依赖注入之属性注入使用备忘

添加服务引用来使用WebService

Git

Git 服务端软件 Gitea 的 Windows 版安装笔记

通过 GitExtensions 来使用 Git 子模块功能

Git 图形化操作之合并提交记录

使用 Git Extensions 简单入门 Git

SVN

SVN 命令行获取提交日志

使用 TortoiseSVN 将某个 SVN 目录下的目录指向另一个仓库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值