编辑
2025-09-18
C#
00

目录

Task.WhenEach 概述
示例一:基本用法
代码解析
示例二:处理任务中出现的异常
代码要点
示例三:使用随机延迟模拟任务完成顺序
说明
总结

在 .NET 异步编程中,通过 async/await 关键字,我们可以轻松地编写非阻塞代码。传统方式下,我们往往使用 Task.WhenAll 来等待所有任务完成,但如果任务耗时各异或我们希望能在任务完成时立即处理,.NET 9 新增的 Task.WhenEach 则提供了一种更加灵活高效的解决方案。本文将带你详细了解 Task.WhenEach 的使用方法,并通过多个示例展示其在不同场景下的应用。


Task.WhenEach 概述

Task.WhenEach 方法接收一个由任务组成的集合,并返回一个 IAsyncEnumerable,可以通过异步 foreach 循环对每个任务进行处理。与传统的等待所有任务完成不同,Task.WhenEach 会在每个任务一完成时就将其依次传递给你的处理逻辑,从而使得程序能够更快速响应每个单独的任务完成事件。


示例一:基本用法

在本示例中,我们通过定义一个名为 PrintWithDelay 的异步方法来模拟延迟任务。方法接受一个整数类型的延迟时间(单位:毫秒),并在延迟结束后返回该值。

下面的示例代码展示了如何创建三个不同延迟的任务,并使用 Task.WhenEach 依次处理它们:

C#
using System; using System.Collections.Generic; using System.Threading.Tasks; namespace TaskWhenEachDemo { class Program { static async Task Main(string[] args) { // 创建一个任务列表,每个任务都会调用 PrintWithDelay 方法,传入不同的延迟时间(毫秒) List<Task<int>> printTasks = new List<Task<int>>() { PrintWithDelay(4000), // 延迟 4000 毫秒 PrintWithDelay(6000), // 延迟 6000 毫秒 PrintWithDelay(2000) // 延迟 2000 毫秒 }; // 使用 Task.WhenEach 方法按任务完成先后依次处理任务结果 await foreach (var task in Task.WhenEach(printTasks)) { // 输出任务的状态和返回结果 Console.WriteLine($"任务状态:{task.Status} | 返回结果:{task.Result}"); } Console.WriteLine("所有任务处理完毕!"); } /// <summary> /// 异步方法:在指定延迟后输出日志,并返回延迟的数值 /// </summary> /// <param name="delay">延迟的时间(毫秒)</param> /// <returns>返回传入的延迟时间</returns> static async Task<int> PrintWithDelay(int delay) { // 使用 Task.Delay 模拟异步操作。await 会等待 delay 毫秒后继续执行。 await Task.Delay(delay); // 控制台打印日志,说明任务已经完成 Console.WriteLine($"延迟 {delay} 毫秒后任务完成!"); // 返回延迟时间作为任务的结果 return delay; } } }

image.png

代码解析

  • PrintWithDelay 方法

    使用 async/await 实现一个简单的延迟任务。当调用此方法时,程序会等待指定毫秒后输出一条日志并返回该延迟值。

  • 创建 Tasks 列表

    我们将多个 PrintWithDelay 调用添加至列表中。每个调用返回一个 Task<int>

  • Task.WhenEach 与 await foreach

    通过 Task.WhenEach 得到任务完成的异步序列,使用 await foreach 循环迭代,当每一个任务完成时,就会马上进入循环体内处理返回结果。


示例二:处理任务中出现的异常

在实际场景中,任务执行过程中可能会出现异常。我们可以通过捕获异常来保证程序的健壮性。下面的示例在异步方法中模拟一个可能抛出异常的场景,并在 Task.WhenEach 循环中对异常进行处理:

C#
namespace AppWhenEach { internal class Program { static async Task Main(string[] args) { // 创建任务列表,其中一个任务将会抛出异常 var tasks = new List<Task<int>>() { PrintWithDelayAndException(3000), // 正常任务 PrintWithDelayAndException(1000, throwError: true), // 会抛出异常 PrintWithDelayAndException(2000) // 正常任务 }; // 通过 Task.WhenEach 依次处理任务结果 await foreach (var task in Task.WhenEach(tasks)) { try { // 此处调用 task.Result 以便获取任务的结果 // 如果任务已抛出异常,此处会触发异常处理 Console.WriteLine($"任务完成,返回结果:{task.Result}"); } catch (Exception ex) { Console.WriteLine($"任务出现异常:{ex.Message}"); } } Console.WriteLine("异常处理示例结束。"); } /// <summary> /// 模拟一个异步任务:延迟一段时间后,有条件地抛出异常或返回延迟值 /// </summary> /// <param name="delay">延迟时间(毫秒)</param> /// <param name="throwError">是否抛出异常</param> /// <returns>任务完成后返回延迟时间</returns> static async Task<int> PrintWithDelayAndException(int delay, bool throwError = false) { await Task.Delay(delay); if (throwError) { // 模拟任务异常发生 throw new InvalidOperationException($"模拟异常:延迟 {delay} 毫秒的任务错误!"); } Console.WriteLine($"延迟 {delay} 毫秒后,任务正常完成!"); return delay; } } }

image.png

代码要点

  • PrintWithDelayAndException 方法中,我们增加了一个可选参数:throwError,当其为 true 时模拟任务异常。
  • 在主程序中,使用 try/catch 捕获由任务返回结果时可能抛出的异常。
  • Task.WhenEach 会依旧按照任务完成的顺序进行迭代,即使有任务因异常提前退出。

示例三:使用随机延迟模拟任务完成顺序

有时我们希望模拟多个任务的不确定完成顺序,下面的示例通过随机生成延迟时间来模拟任务的真实场景:

C#
using System; using System.Collections.Generic; using System.Threading.Tasks; namespace RandomDelayTaskDemo { class Program { static async Task Main(string[] args) { var random = new Random(); // 创建多个任务,每个任务使用随机延迟 List<Task<int>> tasks = new List<Task<int>>(); for (int i = 1; i <= 5; i++) { // 生成 1000 到 5000 毫秒之间的随机延迟 int delay = random.Next(1000, 5000); tasks.Add(PrintWithDelayAndId(i, delay)); } // 通过 Task.WhenEach 按照任务完成时间依次依次处理任务结果 await foreach (var task in Task.WhenEach(tasks)) { Console.WriteLine($"任务返回结果:{task.Result}"); } Console.WriteLine("所有随机延迟任务均已完成。"); } /// <summary> /// 模拟带有编号与延迟的异步任务 /// </summary> /// <param name="id">任务编号</param> /// <param name="delay">延迟时间(毫秒)</param> /// <returns>任务完成后返回延迟时间</returns> static async Task<int> PrintWithDelayAndId(int id, int delay) { Console.WriteLine($"任务 {id} 开始,延迟 {delay} 毫秒。"); await Task.Delay(delay); Console.WriteLine($"任务 {id} 延迟 {delay} 毫秒后完成!"); return delay; } } }

image.png

说明

  • 我们使用 Random 对象生成每个任务的随机延迟,从而让任务的完成顺序不固定,真实模拟并发环境下的执行情况。
  • 依然利用 Task.WhenEach 来按照任务实际完成的顺序逐个处理结果。

总结

.NET 9 中引入的 Task.WhenEach 为异步任务处理提供了一种全新的思路,使得在应用场景中能够更及时地响应单个任务的完成。通过本文示例,我们学习了如何:

  • 利用 async/await 编写异步方法;
  • 使用 Task.WhenEach 枚举任务完成的顺序;
  • 编写健壮的代码处理任务中可能出现的异常;
  • 模拟实际业务场景中任务完成的随机性。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!