我正在尝试使用C# 8提供的新工具更新我的工具集,其中一种似乎特别有用的方法是Task.WhenAll
返回IAsyncEnumerable
. 此方法应在任务结果可用时立即对其进行流式传输,因此命名它WhenAll
没有多大意义。WhenEach
听起来更合适。该方法的签名是:
public static IAsyncEnumerable<TResult> WhenEach<TResult>(Task<TResult>[] tasks);
这种方法可以像这样使用:
var tasks = new Task<int>[]
{
ProcessAsync(1, 300),
ProcessAsync(2, 500),
ProcessAsync(3, 400),
ProcessAsync(4, 200),
ProcessAsync(5, 100),
};
await foreach (int result in WhenEach(tasks))
{
Console.WriteLine($"Processed: {result}");
}
static async Task<int> ProcessAsync(int result, int delay)
{
await Task.Delay(delay);
return result;
}
预期输出:
已处理:5
已处理:4
已处理:1
已处理:3
已处理:2
我设法Task.WhenAny
在循环中使用该方法编写了一个基本实现,但是这种方法存在问题:
public static async IAsyncEnumerable<TResult> WhenEach<TResult>(
Task<TResult>[] tasks)
{
var hashSet = new HashSet<Task<TResult>>(tasks);
while (hashSet.Count > 0)
{
var task = await Task.WhenAny(hashSet).ConfigureAwait(false);
yield return await task.ConfigureAwait(false);
hashSet.Remove(task);
}
}
问题是性能。的实现创建了Task.WhenAny
所提供任务列表的防御性副本,因此在循环中重复调用它会导致 O(n²) 计算复杂度。我幼稚的实现很难处理 10,000 个任务。我的机器上的开销将近 10 秒。我希望该方法几乎与 build-in 一样高效Task.WhenAll
,可以轻松处理数十万个任务。我该如何改进WhenEach
方法以使其表现得体?