问题

执行几个任务,等待它们全部完成。

使用场景

  • 几个独立任务需要同时进行
  • UI界面加载多个模块,并发请求

解决方案

Task.WhenAll 传入若干任务,当所有任务完成时,返回一个完成的任务。

重载方法

  • Task WhenAll(IEnumerable<Task>)
  • Task WhenAll(params Task[])
  • Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>>)
  • Task<TResult[]> WhenAll<TResult>(params Task<TResult>[])

    举例:
            var task1 = Task.Delay(TimeSpan.FromSeconds(1));
var task2 = Task.Delay(TimeSpan.FromSeconds(2));
var task3 = Task.Delay(TimeSpan.FromSeconds(3)); await Task.WhenAll(task1, task2, task3);

当任务返回结果类型相同,所有任务完成返回的是,存着每个任务执行结果的数组。

            var task1 = Task.FromResult(1);
var task2 = Task.FromResult(2);
var task3 = Task.FromResult(3); int[] array = await Task.WhenAll(task1, task2, task3); //{1,2,3}

返回的数组中结果的顺序,并非可控,如上述例子中,只是结果为包含了1,2,3的数组,顺序是不定的。

书中不建议使用以 IEnumerable 类型作为参数的重载。文中没有介绍作者不建议的原因。我找到作者个人博客的一篇文中中提到如下文字(文章地址:https://blog.stephencleary.com/2015/01/a-tour-of-task-part-7-continuations.html

The IEnumerable<> overloads allow you to pass in a sequence of tasks, such as a LINQ expression. The sequence is immediately reified (i.e., copied to an array). For example, this allows you to pass the results of a Select expression directly to WhenAll. Personally, I usually prefer to explicitly reify the sequence by calling ToArray() so that it’s obvious that’s what’s happening, but some folks like the ability to pass the sequence directly in.

该段文字解释了作者更喜欢使用LINQ结合ToArray的方式使用异步,因为作者认为代码会更清晰。书中有例子,如下所示:

        static async Task<string> DownloadAllAsync(IEnumerable<string> urls)
{
var httpClient = new HttpClient();
// 定义每一个 url 的使用方法。
var downloads = urls.Select(url => httpClient.GetStringAsync(url));
// 注意,到这里,序列还没有求值,所以所有任务都还没真正启动。
// 下面,所有的 URL 下载同步开始。
Task<string>[] downloadTasks = downloads.ToArray();
// 到这里,所有的任务已经开始执行了。
// 用异步方式等待所有下载完成。
string[] htmlPages = await Task.WhenAll(downloadTasks);
return string.Concat(htmlPages);
}

如果报错记得添加如下引用

using System.Linq;
using System.Net.Http;

返回的Task的状态

            var task1 = ......;
var task2 = ......;
var task3 = ......; Task allTasks = Task.WhenAll(task1, task2, task3);

以上述伪代码为例说明allTasks的状态

  • 当task1出现异常,异常会抛给allTasks,allTasks的状态同task1状态,也是Faulted。
  • 当task1被取消,allTasks的状态是Canceled
  • 当task1, task2, task3,不出现异常,也未被取消,allTasks的状态是RanToCompletion

Task.WhenAll的异常处理

上面提到了异常处理,当一个task异常,该异常会被allTasks接收,当多个task异常,这些异常也都会被allTasks接收。但是task1抛异常,task2也出异常,但是try catch 处理await Task.WhenAll(task1, task2);只能抓取其中某一个异常。如何获取所有异常呢?书中列举了两种处理方法,代码如下

抛出异常的方法

        static async Task ThrowNotImplementedExceptionAsync()
{
throw new NotImplementedException();
} static async Task ThrowInvalidOperationExceptionAsync()
{
throw new InvalidOperationException();
}

第一种处理方式,只能获取其中一个异常

        static async Task ObserveOneExceptionAsync()
{
var task1 = ThrowNotImplementedExceptionAsync();
var task2 = ThrowInvalidOperationExceptionAsync();
try
{
await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
// ex 要么是 NotImplementedException,要么是 InvalidOperationException
//...
}
}

第二种处理方式,可以获取所有异常

        static async Task ObserveAllExceptionsAsync()
{
var task1 = ThrowNotImplementedExceptionAsync();
var task2 = ThrowInvalidOperationExceptionAsync();
Task allTasks = Task.WhenAll(task1, task2);
try
{
await allTasks;
}
catch
{
AggregateException allExceptions = allTasks.Exception;
//...
}
}

两种方式的区别是,await调用Task.WhenAll返回的Task对象,即例子中的allTasks,代码 await allTasks;

作者在书中将对Task.WhenAll的异常处理放在了讨论中,并说明了自己的处理方式

使用 Task.WhenAll 时,我一般不会检查所有的异常。通常情况下,只处理第一个错误就足够了,没必要处理全部错误。

显然作者更中意第一种方式。那么你呢?

参考文章:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task.whenall?view=netcore-2.2

《C#并发编程经典实例》学习笔记—2.4 等待一组任务完成的更多相关文章

  1. 《C#并发编程经典实例》笔记

    1.前言 2.开宗明义 3.开发原则和要点 (1)并发编程概述 (2)异步编程基础 (3)并行开发的基础 (4)测试技巧 (5)集合 (6)函数式OOP (7)同步 1.前言 最近趁着项目的一段平稳期 ...

  2. 《C#并发编程经典实例》学习笔记—2.7 避免上下文延续

    避免上下文延续 在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行. 为了避免在上下文中恢复运行,可让 await 调用 ConfigureAwait 方法 ...

  3. 《C#并发编程经典实例》学习笔记—3.1 数据的并行处理

    问题 有一批数据,需要对每个元素进行相同的操作.该操作是计算密集型的,需要耗费一定的时间. 解决方案 常见的操作可以粗略分为 计算密集型操作 和 IO密集型操作.计算密集型操作主要是依赖于CPU计算, ...

  4. 《C#并发编程经典实例》学习笔记—2.3 报告任务

    问题 异步操作时,需要展示该操作的进度 解决方案 IProgress<T> Interface和Progress<T> Class 插一段话:读<C#并发编程经典实例&g ...

  5. 《C# 并发编程 · 经典实例》读书笔记

    前言 最近在看<C# 并发编程 · 经典实例>这本书,这不是一本理论书,反而这是一本主要讲述怎么样更好的使用好目前 C#.NET 为我们提供的这些 API 的一本书,书中绝大部分是一些实例 ...

  6. [书籍]用UWP复习《C#并发编程经典实例》

    1. 简介 C#并发编程经典实例 是一本关于使用C#进行并发编程的入门参考书,使用"问题-解决方案-讨论"的模式讲解了以下这些概念: 面向异步编程的async和await 使用TP ...

  7. 《C#并发编程经典实例》学习笔记-关于并发编程的几个误解

    误解一:并发就是多线程 实际上多线程只是并发编程的一种形式,在C#中还有很多更实用.更方便的并发编程技术,包括异步编程.并行编程.TPL 数据流.响应式编程等. 误解二:只有大型服务器程序才需要考虑并 ...

  8. 《C#并发编程经典实例》学习笔记-第一章并发编程概述

    并发编程的术语 并发 同时做多件事情 多线程 并发的一种形式,它采用多个线程来执行程序. 多线程是并发的一种形式,但不是唯一的形式. 并行处理 把正在执行的大量的任务分割成小块,分配给多个同时运行的线 ...

  9. 并发编程概述--C#并发编程经典实例

    优秀软件的一个关键特征就是具有并发性.过去的几十年,我们可以进行并发编程,但是难度很大.以前,并发性软件的编写.调试和维护都很难,这导致很多开发人员为图省事放弃了并发编程.新版.NET 中的程序库和语 ...

  10. 《Java并发编程实战》学习笔记 线程安全、共享对象和组合对象

    Java Concurrency in Practice,一本完美的Java并发参考手册. 查看豆瓣读书 推荐:InfoQ迷你书<Java并发编程的艺术> 第一章 介绍 线程的优势:充分利 ...

随机推荐

  1. 如何实现文件上传 - JavaWeb

    直接上代码 ( idea 开发,SpringBoot 框架 ): 首先是Controller的写法: package com.xxx.Controller; import com.xxx.Tools. ...

  2. Oracle expdp数据泵导出,并在文件上附加上日期格式

    一.导出操作的计算机要安装Oracle Client(建议管理员版本) 二.在服务端创建目录 create directory dpdir as '目录'; 三.给目录赋权限 grant read,w ...

  3. truffle unbox react 出坑指南

    最近几天差点就被这鬼东西给逼疯了,truffle init .truffle unbox webpack 不管我怎么运行都是对的,唯独truffle unbox react 不管在哪个windows都 ...

  4. 关于 Senparc.Weixin.Cache.Redis 引用的 StackExchange.Redis 版本不匹配的反馈测试

    推测原因是老系统中有地方引用了旧版本的 StackExchange.Redis,原因是 StackExchange.Redis 1.2.6 版本未提供针对 .net 4.6 以上的支持,导致库引用会失 ...

  5. 基于LinkedList实现桶排序

    需要考虑以下问题: 1.桶的大小,这里我们可以根据输入的元素的个数来确定桶的大小. 2.怎么样确定当前元素进入哪一个桶,这里我们使用到的是通过一个哈希函数来进行计算. int index = (ele ...

  6. [Swift]LeetCode170.两数之和III - 数据结构设计 $ Two Sum III - Data structure design

    Design and implement a TwoSum class. It should support the following operations:add and find. add - ...

  7. [Swift]LeetCode717. 1比特与2比特字符 | 1-bit and 2-bit Characters

    We have two special characters. The first character can be represented by one bit 0. The second char ...

  8. shell 删除重复文件脚本

    摘自 <Linux Shell脚本攻略>一书,例子在109页,原理在110页,原理讲解的很好哦! 需要了解awk命令.xargs,脚本中所用的命令在Linux Shell脚本攻略一书中都有 ...

  9. python网络-TFTP客户端开发(25)

    一. TFTP协议介绍 TFTP(Trivial File Transfer Protocol,简单文件传输协议) 是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议 特点: ...

  10. python获取当前路径

    python获取当前执行命令的路径: #!/usr/bin/env python # -*# coding: utf-8 -*- import os print os.getcwd() python获 ...