Task.WhileAll扩展方法
TPL实现Task.WhileAll扩展方法
文章翻译整理自 Nikola Malovic 两篇博文:
当 Task.WhenAll 遇见 Task.WhenAny
在 TPL (Task Parallel Library) 中,有两种通过非阻塞方式等待 Task 数组任务结束的方式:Task.WhenAll 和 Task.WhenAny 。
它们的工作方式是:
- WhenAll 当每项任务都完成时为完成。
- WhenAny 当任意项任务完成时为完成。
现在我们需要一项功能,完成 Task 数组中的所有任务,并且当有任务完成时汇报状态。
我们称这个扩展方法为:Task.WhileAll 。
扩展方法实现

1 public static class TaskExtensions
2 {
3 public static async Task<IList<T>> WhileAll<T>(this IList<Task<T>> tasks, IProgress<T> progress)
4 {
5 var result = new List<T>(tasks.Count);
6 var done = new List<Task<T>>(tasks);
7
8 while (done.Count > 0)
9 {
10 await Task.WhenAny(tasks);
11
12 var spinning = new List<Task<T>>(done.Count - 1);
13 for (int i = 0; i < done.Count; i++)
14 {
15 if (done[i].IsCompleted)
16 {
17 result.Add(done[i].Result);
18 progress.Report(done[i].Result);
19 }
20 else
21 {
22 spinning.Add(done[i]);
23 }
24 }
25
26 done = spinning;
27 }
28
29 return result;
30 }
31 }

代码实现很简单:
- 其是 IList<Task<T>> 的一个 async 扩展方法
- 方法返回完整的 IList<T> 结果
- 方法会接受一个 IProgress<T> 类型的参数,用于向订阅者发布 Task 完成信息
- 在方法体内,我们使用一个循环来检测,直到所有 Task 完成
- 通过使用 Task.WhenAny 来异步等待 Task 完成
单元测试

1 [TestClass]
2 public class UnitTest1
3 {
4 [TestMethod]
5 public async Task TestTaskExtensionsWhileAll()
6 {
7 var task1 = Task.Run(() => 101);
8 var task2 = Task.Run(() => 102);
9 var tasks = new List<Task<int>>() { task1, task2 };
10
11 List<int> result = new List<int>();
12 var listener = new Progress<int>(
13 taskResult =>
14 {
15 result.Add(taskResult);
16 });
17
18 var actual = await tasks.WhileAll(listener);
19 Thread.Sleep(50); // wait a bit for progress reports to complete
20
21 Assert.AreEqual(2, result.Count);
22 Assert.IsTrue(result.Contains(101));
23 Assert.IsTrue(result.Contains(102));
24
25 Assert.AreEqual(2, actual.Count);
26 Assert.IsTrue(actual.Contains(101));
27 Assert.IsTrue(actual.Contains(102));
28 }
29 }

同样,测试代码也不复杂:
- 创建两个哑元 Task,并存到数组中
- 定义进度侦听器 Progress<T>,来监测每个任务运行的结果
- 通过 await 方式来调用方法
- 使用 Thread.Sleep 来等待 50ms ,以便 Progress 可以来得及处理结果
- 检查所有 Task 执行完毕后均已上报 Progress
- 检查所有 Task 均已执行完毕
我知道每当使用 Thread.Sleep 时绝不是件好事,所以我决定摆脱它。
实现IProgressAsync<T>
问题实际上是因为 IProgress<T> 接口定义的是 void 委托,因此无法使用 await 进行等待。
因此我决定定义一个新的接口,使用同样的 Report 行为,但会返回 Task ,用以实现真正的异步。
1 public interface IProgressAsync<in T>
2 {
3 Task ReportAsync(T value);
4 }
有了异步版本的支持,将使订阅者更容易处理 await 调用。当然也可以使用 async void 来达成,但我认为 async void 总会延伸出更差的设计。所以,我还是选择通过定义 Task 返回值签名的接口来达成这一功能。
如下为接口实现:

1 public class ProgressAsync<T> : IProgressAsync<T>
2 {
3 private readonly Func<T, Task> handler;
4
5 public ProgressAsync(Func<T, Task> handler)
6 {
7 this.handler = handler;
8 }
9
10 public async Task ReportAsync(T value)
11 {
12 await this.handler.InvokeAsync(value);
13 }
14 }

显然也没什么特别的:
- 使用 Func<T, Task> 来代替 Action<T>,以便可以使用 await
- ReportAsync 通过使用 await 方式来提供 Task
有了这些之后,我们来更新扩展方法:

1 public static class TaskExtensions
2 {
3 public static async Task<IList<T>> WhileAll<T>(this IList<Task<T>> tasks, IProgressAsync<T> progress)
4 {
5 var result = new List<T>(tasks.Count);
6 var remainingTasks = new List<Task<T>>(tasks);
7
8 while (remainingTasks.Count > 0)
9 {
10 await Task.WhenAny(tasks);
11 var stillRemainingTasks = new List<Task<T>>(remainingTasks.Count - 1);
12 for (int i = 0; i < remainingTasks.Count; i++)
13 {
14 if (remainingTasks[i].IsCompleted)
15 {
16 result.Add(remainingTasks[i].Result);
17 await progress.ReportAsync(remainingTasks[i].Result);
18 }
19 else
20 {
21 stillRemainingTasks.Add(remainingTasks[i]);
22 }
23 }
24
25 remainingTasks = stillRemainingTasks;
26 }
27
28 return result;
29 }
30
31 public static Task InvokeAsync<T>(this Func<T, Task> task, T value)
32 {
33 return Task<Task>.Factory.FromAsync(task.BeginInvoke, task.EndInvoke, value, null);
34 }
35 }

所有都就绪后,我们就可以将 Thread.Sleep 从单元测试中移除了。

1 [TestClass]
2 public class UnitTest1
3 {
4 private List<int> result = new List<int>();
5 private async Task OnProgressAsync(int arg)
6 {
7 result.Add(arg);
8 }
9
10 [TestMethod]
11 public async Task TestTaskExtensionsWhileAll()
12 {
13 var task1 = Task.Run(() => 101);
14 var task2 = Task.Run(() => 102);
15 var tasks = new List<Task<int>>() { task1, task2 };
16
17 var listener = new ProgressAsync<int>(this.OnProgressAsync);
18 var actual = await tasks.WhileAll(listener);
19
20 Assert.AreEqual(2, this.result.Count);
21 Assert.IsTrue(this.result.Contains(101));
22 Assert.IsTrue(this.result.Contains(102));
23
24 Assert.AreEqual(2, actual.Count);
25 Assert.IsTrue(actual.Contains(101));
26 Assert.IsTrue(actual.Contains(102));
27 }
28 }

Task.WhileAll扩展方法的更多相关文章
- TPL实现Task.WhileAll扩展方法
文章翻译整理自 Nikola Malovic 两篇博文: Task.WhileAll Awaitable task progress reporting 当 Task.WhenAll 遇见 Task. ...
- Task扩展方法取消操作
/// <summary> /// 任务扩展,传入取消操作 /// </summary> public static class TaskExtensionDemo { //因 ...
- .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类
.NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类 0x00 为什么要引入扩展方法 有的中间件功能比较简单,有的则比较复杂,并且依赖其它组件.除 ...
- 【转载】.NET(C#): Task.Unwrap扩展方法和async Lambda
.NET(C#): Task.Unwrap扩展方法和async Lambda 目录 Task.Unwrap基本使用 Task.Factory.StartNew和Task.Run的Unwrap操作 使用 ...
- 用UseMiddleware扩展方法注册中间件类
用UseMiddleware扩展方法注册中间件类 .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类 0x00 为什么要引入扩展方法 有的中间件 ...
- 【ABP杂烩】Extensions后缀扩展方法
1.Extensions介绍 扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型. 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用 ...
- ABP框架源码中的Linq扩展方法
文件目录:aspnetboilerplate-dev\aspnetboilerplate-dev\src\Abp\Collections\Extensions\EnumerableExtensions ...
- ASP.NET Core中,UseDeveloperExceptionPage扩展方法会吃掉异常
在ASP.NET Core中Startup类的Configure方法中,有一个扩展方法叫UseDeveloperExceptionPage,如下所示: // This method gets call ...
- 17-EasyNetQ:非泛型的发布&订阅扩展方法
自从EasyNetQ第一个版本开始,它就可以发布/订阅特定类型的消息. bus.Subscribe<MyMessage>("subscriptionId", x =&g ...
随机推荐
- Routing路由
Routing路由 新版Routing功能介绍 在ASP.NET 5和MVC6中,Routing功能被全部重写了,虽然用法有些类似,但和之前的Routing原理完全不太一样了,该Routing框架不仅 ...
- 离github在导入dubbo工程
dubbo移动到github目前已经有一些时间,我们可能根本不习惯使用github.因此,我们仍然使用以前的版本号. 由于某些原因.阿里下载前关闭.在这里给大家提供一个私有地址: 链接:http:// ...
- MVC6项目
解读ASP.NET 5 & MVC6系列(2):初识项目 2015-05-14 09:08 by 汤姆大叔, 2866 阅读, 19 评论, 收藏, 编辑 初识项目 打开VS2015,创建We ...
- Linux_下安装MySQL
linux下mysql 最新版安装图解教程 1.查看当前安装的linux版本 命令:lsb_release -a 如下图所示 通过上图中的数据可以看出安装的版本为RedHat5.4,所以我们需要下载R ...
- Group and sum array of hashes by date
I have an array of hashes like this: [{:created=>Fri, 22 Jan 2014 13:02:13 UTC +00:00, :amount=&g ...
- 关于fork()函数的作用
(1) 先看一个实例: #include <unistd.h>; #include <sys/types.h>; main () { pid_t pi ...
- archlinux的wiki非常强壮
最近发现搜索linux工具或系统配置内容.来自同一个站点很多很好的资源:https://www.archlinux.org/,网站wiki(https://wiki.archlinux.org/)中有 ...
- 关于ios的autoLayout的一些简单介绍以及使用方法
一.autoLayout的用途: 主要用于屏幕适配,尤其是出现了iphone6,plus之后. 二.怎么简单的用autoLayout呢? 点击左一,可以看到: 点击左二: 基本上要想autolayou ...
- Light OJ 1316 A Wedding Party 最短路+状态压缩DP
题目来源:Light OJ 1316 1316 - A Wedding Party 题意:和HDU 4284 差点儿相同 有一些商店 从起点到终点在走过尽量多商店的情况下求最短路 思路:首先预处理每两 ...
- Fragment 设置主题
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanc ...