在 .NET 异步编程中,通过 async/await 关键字,我们可以轻松地编写非阻塞代码。传统方式下,我们往往使用 Task.WhenAll
来等待所有任务完成,但如果任务耗时各异或我们希望能在任务完成时立即处理,.NET 9 新增的 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;
}
}
}
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;
}
}
}
PrintWithDelayAndException
方法中,我们增加了一个可选参数:throwError,当其为 true 时模拟任务异常。有时我们希望模拟多个任务的不确定完成顺序,下面的示例通过随机生成延迟时间来模拟任务的真实场景:
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;
}
}
}
.NET 9 中引入的 Task.WhenEach 为异步任务处理提供了一种全新的思路,使得在应用场景中能够更及时地响应单个任务的完成。通过本文示例,我们学习了如何:
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!