在现代 .NET 应用程序开发中,异步编程(Asynchronous Programming)已成为提升性能、改善响应能力和充分利用多核处理器的关键技术。asyncawait 关键字极大地简化了异步代码的编写,而 Task 类则是这一模型的核心。在处理多个并发操作时,Task.WhenAll 方法是一个强大且常用的工具。本文将深入探讨 Task.WhenAll 的工作原理、使用场景、最佳实践以及需要注意的陷阱。

什么是 Task.WhenAll

Task.WhenAllTask 类提供的一个静态方法,用于等待多个任务(TaskTask<T>)全部完成。它接收一个任务集合(如 IEnumerable<Task>Task[]),并返回一个新的 TaskTask<TResult[]>。当传入的任务全部成功完成时,返回的任务才会完成。如果其中任何一个任务因异常而失败,返回的任务也会以异常状态完成。

基本语法

// 等待多个无返回值的任务完成
Task WhenAll(params Task[] tasks);
Task WhenAll(IEnumerable<Task> tasks);// 等待多个有返回值的任务完成
Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks);
Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks);

为什么需要 Task.WhenAll

在没有 Task.WhenAll 的情况下,我们可能会这样处理多个异步操作:

// ❌ 错误方式:顺序执行,效率低下
var result1 = await GetDataAsync(url1);
var result2 = await GetDataAsync(url2);
var result3 = await GetDataAsync(url3);
// 所有操作是串行的,总耗时约等于各操作耗时之和

或者使用 Task.WhenAny,但它只等待第一个完成的任务,无法满足“全部完成”的需求。

Task.WhenAll 允许我们并发地启动所有操作,然后等待它们全部结束,从而显著减少总执行时间。

✅ 正确方式:使用 Task.WhenAll

// 启动所有任务(并发)
var task1 = GetDataAsync(url1);
var task2 = GetDataAsync(url2);
var task3 = GetDataAsync(url3);// 等待所有任务完成
await Task.WhenAll(task1, task2, task3);// 所有任务都已完成,可以安全地获取结果
var result1 = await task1; // 不会阻塞,因为任务已结束
var result2 = await task2;
var result3 = await task3;

或者更简洁地:

var tasks = new[]
{GetDataAsync(url1),GetDataAsync(url2),GetDataAsync(url3)
};await Task.WhenAll(tasks);
// 结果按任务在数组中的顺序排列
var results = await Task.WhenAll(tasks); // 对于有返回值的任务

核心优势

  1. 性能提升:通过并发执行,总耗时通常接近于最慢的那个任务的耗时,而不是所有任务耗时之和。
  2. 代码简洁:避免了复杂的 Task.ContinueWith 链或手动管理多个 TaskCompletionSource
  3. 异常处理集中:所有任务的异常都可以在 await Task.WhenAll(...) 处被捕获(通常包装在 AggregateException 中)。
  4. 结果聚合:对于 Task<T>WhenAll 直接返回一个包含所有结果的数组,顺序与输入任务一致。

重要注意事项与最佳实践

1. 任务必须已经启动

Task.WhenAll 等待的是已经启动的任务。确保你在调用 Task.WhenAll 之前已经启动了所有任务(即调用了异步方法但没有 await 它)。

// ✅ 正确:任务已启动
var task1 = SomeAsyncOperation(); // 启动任务
var task2 = AnotherAsyncOperation(); // 启动任务
await Task.WhenAll(task1, task2);// ❌ 错误:任务未启动(SomeAsyncOperation 返回的是 Task,但未执行)
// await Task.WhenAll(SomeAsyncOperation(), AnotherAsyncOperation()); 
// 这行代码本身会启动任务,但写法不直观,建议分开写。

2. 异常处理

Task.WhenAll 返回的任务在任何一个输入任务失败时都会失败。异常通常被包装在 AggregateException 中。推荐使用 try-catch 块来处理:

try
{await Task.WhenAll(task1, task2, task3);
}
catch (Exception ex)
{// ex 可能是 AggregateException 或单个异常(.NET 4.5+ 有时会扁平化)// 检查 ex.InnerException 或 ex.Flatten().InnerExceptions 来获取具体异常Console.WriteLine($"一个或多个任务失败: {ex.Message}");// 可以遍历所有任务检查其状态foreach (var task in new[] { task1, task2, task3 }){if (task.IsFaulted){Console.WriteLine($"任务 {task.Id} 失败: {task.Exception?.InnerException?.Message}");}}
}

3. 性能考量:避免不必要的 Task.WhenAll

如果任务数量很少(比如1-2个),直接 await 每个任务可能更清晰。Task.WhenAll 在处理多个(例如3个以上)独立任务时优势最明显。

4. 与 Task.WhenAny 的区别

  • Task.WhenAll: 等待所有任务完成。
  • Task.WhenAny: 等待任何一个任务完成。适用于“竞态”场景,比如从多个数据源获取数据,取最快返回的结果。

5. 内存与资源管理

启动大量并发任务可能会耗尽系统资源(如线程池线程、网络连接、文件句柄)。考虑使用 SemaphoreSlimParallel.ForEachAsync (C# 11+) 来限制并发度。

后续其他文章再展开讲解(这里先挖个坑~~~~,可私信我填坑,我怕忘记了。)

6. 创建一个带有默认值的已完成任务!

有的时候,在进入其他分支的时候,某个task可能是null,这样Task.WhenAll等待的时候就会报错,我们需要创建一个带有默认值的已完成任务.
比如:

Task task3 = Task.FromResult(default(bool))
  1. default(bool)
    default 是 C# 中的一个关键字,用于获取类型的默认值。
    对于值类型 bool,其默认值是 false。
    所以,default(bool) 等价于 false。
  2. Task.FromResult(T result)
    这是 Task 类的一个静态方法。
    它接收一个类型为 T 的参数 result。
    它返回一个 Task 对象,这个对象的状态是已完成(RanToCompletion),并且其 Result 属性的值就是传入的 result。
    这个方法非常高效,因为它不会创建一个新的线程或启动任何异步工作;它只是包装一个已经存在的值到一个 Task 的壳子里。
  3. Task.FromResult(default(bool))
    将两者结合起来,Task.FromResult(default(bool)) 创建并返回一个 Task。
    这个 Task 立即完成。
    当你 await 这个任务时,你会立即得到 false。

7 混合处理无返回值与有返回值的任务

当我们需要同时等待一个无返回值的 Task 和一个有返回值的 Task 时,Task.WhenAll 仍然适用,但获取返回值该怎么做呢?

实现方式​

Task.WhenAll 可以接收混合类型的任务(Task 和 Task),但返回的是 Task 而非 Task<T[]>。因此,我们需要通过原始的有返回值任务的引用获取结果。

using System;
using System.Threading.Tasks;class Program
{static async Task Main(string[] args){// 无返回值的任务Task task1 = Task.Run(() =>{Console.WriteLine("任务1(无返回值)开始");Task.Delay(2000).Wait();Console.WriteLine("任务1完成");});// 有返回值的任务(返回bool)Task<bool> task2 = Task.Run(() =>{Console.WriteLine("任务2(有返回值)开始");Task.Delay(1500).Wait();Console.WriteLine("任务2完成");return true; // 返回bool结果});// 同时等待两个任务完成await Task.WhenAll(task1, task2);// 单独获取有返回值的任务结果bool result = await task2; // 更推荐的异步方式//bool result = task2.Result;  这么写也是可以的!!!!!!Console.WriteLine($"任务2的返回值:{result}");Console.WriteLine("所有任务都已完成");}
}

最后给一个实际的例子

  /// <summary>/// 采集图片进行推理/// </summary>/// <param name="index">对应的相机:0 正面相机; 1 反面相机</param>/// <returns></returns>async Task<bool> RunOne(int index){Stopwatch 计时器 = new Stopwatch();List<YOLOData> data = new List<YOLOData>();CameraConfig info;GraphicInfo graphic;//YOLODetection yolo;HObject Hobj;try{info = GlobalData.Instance.saveInfo.NeedOpenedCameraList[index];graphic = GlobalData.Instance.saveInfo.Graphics.First(t=>t.SerialNumber == info.SerialNumber);//yolo = graphic.yolo;}catch (Exception){throw new Exception($"NeedOpenedCameraList或者graphic,未找到第{index}个相机");}Task task1, task2;Task<bool> task3 = Task.FromResult(default(bool));try{计时器.Restart();//----触发运动(从初始位置到结束位置)await motionCard.PmoveEx(axisConfigInfo, axisConfigInfo.Positions[0].Value);logger.Info($"开始运动到{axisConfigInfo.Positions[0].Value}");//触发一次采集!CameraService.Snap(info.SerialNumber);//----触发运动(从结束位置到初始位置)task1 = motionCard.PmoveEx(axisConfigInfo, axisConfigInfo.Positions[1].Value);logger.Info($"结束运动到{axisConfigInfo.Positions[1].Value}");//采集图片Hobj = CameraService.GetHImage(info.SerialNumber, info.Timeout);logger.Info($"采集到图片!");计时器.Stop();//----触发运动(从初始位置到结束位置)task2 = motionCard.PmoveEx(axisConfigInfo, axisConfigInfo.Positions[0].Value);logger.Info($"回到开始位置!{axisConfigInfo.Positions[0].Value}");}catch (Exception ex){MessageBox.Show($"运动采集失败{ex.Message}");return false;}var t1 = 计时器.ElapsedMilliseconds;bool b = false;if (Hobj != null){task3 = ImgCheck(graphic, Hobj);}else{MainWindowViewModel.PostGrowlEvent(info.SerialNumber + "相机获取图片失败", EnumAlarmType.Warning);Task.Delay(1000).Wait();b = false;}await Task.WhenAll(task1, task2, task3);//获取结果b = task3.Result;logger.Info($"完成一次检测~~~~~结果为:{b}");return b;}

总结

Task.WhenAll 是 .NET 异步编程工具箱中不可或缺的一部分。它通过并发执行多个独立的异步操作,极大地提升了应用程序的效率和响应能力。正确理解和使用 Task.WhenAll,能够让你的代码更加高效、简洁和健壮。

核心要点回顾

  • 并发启动,然后等待全部完成
  • 显著减少总执行时间
  • 集中处理异常聚合结果
  • 注意任务启动时机异常处理
  • 根据场景控制并发度

掌握 Task.WhenAll,让你的 .NET 应用在处理多任务时游刃有余!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/918098.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/918098.shtml
英文地址,请注明出处:http://en.pswp.cn/news/918098.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微型导轨在半导体制造中有哪些高精密应用场景?

微型导轨在半导体制造中用于晶圆对准和定位系统&#xff0c;确保晶圆在光刻、蚀刻等工艺中精确移动。其高精度、高刚性、低摩擦和紧凑设计等特性&#xff0c;使其成为半导体设备实现微米级运动控制的核心部件。光刻机&#xff1a;在光刻工艺中&#xff0c;微型导轨支撑并引导掩…

全栈:Tomcat 安装教程

Tomcat 安装教程 安装 Tomcat 的步骤因操作系统而异&#xff0c;以下是 Windows、Linux 和 Mac 系统的详细安装方法&#xff1a; 一、Windows 系统安装 Tomcat 下载 Tomcat 访问 Tomcat 官方网站&#xff08;http://tomcat.apache.org/&#xff09;&#xff0c;选择适合的版本…

数据分析——Pandas库

Pandas是Python生态系统中最强大、最流行的数据分析库&#xff0c;专为处理结构化数据&#xff08;如表格和时间序列&#xff09;而设计。它提供了高效的数据结构和丰富的功能&#xff0c;使得数据清洗、转换、分析和可视化变得简单直观。一、Pandas库的安装详解1. 安装前的准备…

数据结构-哈希表(散列表)

1.基本概念哈希表&#xff08;散列表&#xff09;&#xff1a;提高数据的查找效率哈希存储&#xff1a;将要存储的数据的关键字和存储位置之间&#xff0c;建立起对应的关系&#xff0c; 这个关系称之为哈希函数。存储数据时&#xff0c;通过对应的哈希函数可以将数据映射到指定…

如何在Vue中使用拓扑图功能

前言 该组件基于 Vue.js 和 AntV G6 构建项目特色功能 1. 丰富的节点图标支持 本拓扑图系统的最大特色是支持使用自定义图片作为节点图标 2. 智能的力导向布局 系统采用力导向布局算法&#xff0c;能够自动优化节点位置&#xff0c;避免重叠&#xff0c;形成美观的网络拓扑结构…

基于dynamic的Druid 与 HikariCP 连接池集成配置区别

你提供的内容是关于 ​​dynamic-datasource-spring-boot-starter​​ 的详细介绍&#xff0c;这是一个非常实用的 ​​Spring Boot 多数据源动态切换组件​​&#xff0c;适用于需要在单个应用中连接多个数据库并灵活切换数据源的场景。下面我为你梳理一下该组件的核心信息与使…

算法训练之栈

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨个人…

OpenAI 最新开源模型 gpt-oss (Windows + Ollama/ubuntu)本地部署详细教程

OpenAI 最近发布了其首个开源的开放权重模型gpt-oss&#xff0c;这在AI圈引起了巨大的轰动。对于广大开发者和AI爱好者来说&#xff0c;这意味着我们终于可以在自己的机器上&#xff0c;完全本地化地运行和探索这款强大的模型了。 本教程将一步一步指导你如何在Windows系统上&…

在X86架构Linux中创建虚拟根目录并下载指定架构(如aarch64)的软件包(含依赖)

在X86架构Linux中创建虚拟根目录并下载指定架构(如aarch64)的软件包(含依赖) 在Linux系统中&#xff0c;有时候我们需要在特定的环境或架构下安装软件包&#xff0c;而不影响主系统。一种常见的方法是创建一个虚拟的根目录&#xff0c;并在此环境中操作。本文将介绍如何通过创建…

scratch笔记和练习-第9课:一起来绘画

位图也称为点阵图&#xff0c;它是由许许多多的点组成的&#xff0c;这些点被称为像素。位图图像可以表现丰富的多彩变化 并产生逼真的效果&#xff0c;很容易在不同软件之间交换使用&#xff0c; 但它在保存图像时需要记录每一个像素的色彩信息&#xff0c;所以占用的存储空间…

[linux] Linux:一条指令更新DDNS

Linux&#xff1a;一条指令更新DDNS 在动态IP环境下&#xff0c;如何确保我们的域名始终指向正确的公网IP地址&#xff1f;动态DNS&#xff08;DDNS&#xff09;服务为我们提供了完美的解决方案。今天&#xff0c;我将分享一个简洁高效的Linux命令行指令&#xff0c;用于自动更…

[激光原理与应用-182]:测量仪器 - 光束型 - 光束质量分析仪

光束质量分析仪是用于精确评估激光光束特性的核心设备&#xff0c;通过测量光束的强度分布、相位分布、发散角等参数&#xff0c;为激光系统的优化、加工工艺控制及科研实验提供关键数据支持。以下是光束质量分析仪的详细解析&#xff1a;一、核心功能 - 光束强度分布分析测量内…

Linux 限制 root 登录 IP 地址的方法

Linux 限制 root 登录 IP 地址的方法Linux 限制 root 登录 IP 地址的方法方法一&#xff1a;修改 SSH 配置文件方法二&#xff1a;使用 hosts.allow 和 hosts.deny 文件方法三&#xff1a;使用防火墙规则方法四&#xff1a;使用 access.conf 文件注意事项Linux 限制 root 登录 …

Word中怎样插入特殊符号

使用 “插入” 菜单&#xff1a;插入常用符号&#xff1a;将光标置于要插入符号的位置&#xff0c;点击 “插入” 选项卡&#xff0c;在 “符号” 组中点击 “符号” 按钮&#xff0c;会弹出一个符号库&#xff0c;里面包含了常见的标点符号、特殊字符等&#xff0c;找到所需符…

Linux 内核发包流程与路由控制实战

Linux 内核发包流程与路由控制实战 在网络调优、性能优化、SDN、NFV、容器网络等场景下&#xff0c;理解 Linux 内核发包路径和路由控制机制是必修课。 本文将从内核网络栈的原理入手&#xff0c;再结合 iproute2 命令和 策略路由给出实战案例。一、Linux 内核发包流程&#xf…

点播服务器

早期的时候&#xff0c;用 live555 作为 rtsp 点播服务器&#xff1b;现在比较常用的 流媒体服务器比较多&#xff1b;这里比较简单的&#xff0c;可以用 ZLMediakit&#xff1b;可以支持 ffmeg 退流 到ZLMediakit&#xff0c;然后别的客户端从 ZLMediakit 服务器拉流&#xff…

分享超图提供的、很不错的WebGIS学习资源

最近在学习了解Supermap iclient&#xff0c;发现官方提供的帮助文档、GIS学堂真的不错&#xff0c;解释了很多的内容。 官方modern-web-gis-in-action文档的网址如下&#xff1a;https://iclient.supermap.io/web/books/modern-web-gis-in-action/&#xff0c;在其中介绍了现代…

通信算法之298: verilog语法generate和for介绍

在 Verilog 中&#xff0c;generate和for是实现参数化设计和模块实例化复用的重要工具&#xff0c;尤其在需要根据参数动态生成逻辑时非常有用。以下是它们的使用方法和区别&#xff1a;1. for循环&#xff08;过程块内&#xff09;for循环主要用于过程块&#xff08;always/in…

laravel在cli模式下输出格式漂亮一些

在 Laravel 的 CLI 模式下&#xff0c;可以通过以下方式让命令行输出更加美观和专业&#xff1a; 1. 使用 Artisan 输出助手方法 Laravel 提供了多种输出样式方法&#xff1a; public function handle() {// 基础样式$this->info(成功信息 - 绿色); // 绿色$this->err…

大数据管理与应用学什么?就业前景怎么样?

前言在数字经济蓬勃发展的今天&#xff0c;大数据已经成为推动社会进步的核心生产要素。大数据管理与应用作为新兴交叉学科&#xff0c;正受到越来越多学生和企业的关注。本文将全面剖析该专业的课程体系、核心技能要求&#xff0c;详细介绍CDA数据分析师认证的备考策略&#x…