.NET 9 中的 Task.WhenEach

.NET 9 中的 Task.WhenEach

Intro

在 .NET 9 中引入了 Task.WhenEach , 有多个任务时可以方便地流式方式处理多个 Task

API

namespace System.Threading.Tasks;

public class Task
{
    public static IAsyncEnumerable<Task> WhenEach(params Task[] tasks);
    public static IAsyncEnumerable<Task> WhenEach(params ReadOnlySpan<Task> tasks);
    public static IAsyncEnumerable<Task> WhenEach(IEnumerable<Task> tasks);

    public static IAsyncEnumerable<Task<TResult>> WhenEach(params Task<TResult>[] tasks);
    public static IAsyncEnumerable<Task<TResult>> WhenEach(params ReadOnlySpan<Task<TResult>> tasks);
    public static IAsyncEnumerable<Task<TResult>> WhenEach(IEnumerable<Task<TResult>> tasks);
}

新的 API 不只支持了 IEnumerable 和 array,也支持了 C# 13 中的 params span 的新特性

Sample

一个简单的使用示例如下:

var startTimestamp = TimeProvider.System.GetTimestamp();
var tasks = Enumerable.Range(0, 5)
    .Select(i => Task.Delay(TimeSpan.FromSeconds(i + 1)))
    ;
await foreach (var item in Task.WhenEach(tasks))
{
    Console.WriteLine(item.IsCompletedSuccessfully);
    Console.WriteLine(TimeProvider.System.GetLocalNow());
}
var elapsedTime = TimeProvider.System.GetElapsedTime(startTimestamp);
Console.WriteLine(elapsedTime);

输出结果如下:

a897bc9b0ff1380759700e2d3f0ac810.png

在没有这个 API 之前,我们可能就要通过 Task.WhenAll 来处理或者像 issue 里那种写法

直接使用 Task.WhenAll 效率会有点低,性能会有折扣

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
while (downloadTasks.Any())
{
    Task<int> finishedTask = await Task.WhenAny(downloadTasks);
    downloadTasks.Remove(finishedTask);
    Process(await finishedTask);
}

或者通过 Task Continuation + WhenAll 的方式处理

var startTimestamp = TimeProvider.System.GetTimestamp();
var tasks = Enumerable.Range(0, 5)
    .Select(i => Task.Delay(TimeSpan.FromSeconds(i + 1))
        .ContinueWith(r =>
        {
            Console.WriteLine(r.IsCompletedSuccessfully);
            Console.WriteLine(TimeProvider.System.GetLocalNow());
        })
    );
await Task.WhenAll(tasks);
var elapsedTime = TimeProvider.System.GetElapsedTime(startTimestamp);
Console.WriteLine(elapsedTime);

有了新的 API 之后,就可以比较方便高效的进行处理了,前面是一个没有返回的示例,带有返回值的我们也可以使用这一 API ,我们稍微改造一下

Console.WriteLine(TimeProvider.System.GetLocalNow());
var tasks = Enumerable.Range(0, 5)
 .Select(i =>
 {
     return Task.Delay(TimeSpan.FromSeconds(i + 1))
       .ContinueWith(r => i);
 })
 ;
await foreach (var item in Task.WhenEach(tasks))
{
 Console.WriteLine(item.Result);
 await Task.Delay(TimeSpan.FromSeconds(1));
}
Console.WriteLine(TimeProvider.System.GetLocalNow());

输出结果如下:

9b6aae752839e653af5c9a033dedc5e9.png

output-generic-task

这个示例一共的时间是 6 秒,如果我们直接用 Task.WhenAll 再执行我们的逻辑,那么我们的时间应该就是 5s + 5s = 10s 了,使用这一 API 可以在 task 完成的时候及时进行处理从而提高 task 的性能

References

  • https://github.com/dotnet/runtime/issues/61959

  • https://github.com/dotnet/runtime/pull/100316

  • https://github.com/WeihanLi/SamplesInPractice/blob/main/net9sample/Net9Samples/TaskSample.cs#L90

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值