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

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

独立观察员 2023 年 3 月 28 日


一、起因

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


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


二、疑惑

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

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


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


按照作者的说法:

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


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


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

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


三、实验

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


如图所示,本次将要演示五种情况,每种情况最后都是用 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 组:


再来看看 Task.Run 组:


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


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

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


四、结论

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

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/


感谢阅读。


原创文章,转载请注明: 转载自 独立观察员 (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 目录下的目录指向另一个仓库


相关文章

经脉疏通:异步编程与多线程心法(异步编程好处)

"前情提要:修士李四强行同步调用异步功法,导致经脉(线程)阻塞,全身灵力(CPU资源)停滞,化作一尊代码石像...今日我们修习async/await无上心法,打通并发任督二脉!"本章修...

C++11多线程编程(四)——原子操作

今天和大家说说C++多线程中的原子操作。首先为什么会有原子操作呢?这纯粹就是C++这门语言的特性所决定的,C++这门语言是为性能而生的,它对性能的追求是没有极限的,它总是想尽一切办法提高性能。互斥锁是...

python多进程编程(python 多进程处理数据)

forkwindows中是没有fork函数的,一开始直接在Windows中测试,直接报错import os import time ret = os.fork() if ret == 0:...

一文扫盲!Python 多线程的正确打开方式

一、多线程:程序世界的 "多面手"(一)啥是多线程?咱先打个比方,你去餐厅吃饭,一个服务员同时接待好几桌客人,每桌客人就是一个 "线程",服务员同时处理多桌事务就是 &...

有趣的安全实验:利用多线程资源竞争技术上传shell

通过多线程资源竞争的手段同时上传两个头像,就可以在Apache+Rails环境下实现远程代码执行。这并不是天方夜谭,同时我相信许多文件上传系统都会有这个漏洞……这是一个非常有趣的安全实验,一起来看看吧...

基于LabVIEW多线程的织物疵点视觉检测系统

李庆,谢一首,郑力新,张裕坤,庄礼鸿(华侨大学 工业智能化技术与系统福建省高校工程研究中心,福建 泉州 362021)摘要:设计了一种利用机器视觉,并且结合LabVIEW多线程处理机制与Hough变换...