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

一、起因

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

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

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

二、疑惑

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

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

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

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

按照作者的说法:

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

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

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

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

三、实验

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

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

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

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

再来看看 Task.Run 组:

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

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

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

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

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

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

四、结论

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

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/

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

感谢阅读。