【发布时间】:2020-01-31 07:14:49
【问题描述】:
我正在尝试使用C# 8 提供的新工具更新我的工具集,其中一种似乎特别有用的方法是返回IAsyncEnumerable 的Task.WhenAll 版本。此方法应在任务结果可用时立即对其进行流式传输,因此将其命名为 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 的 implementation 创建所提供任务列表的防御性副本,因此在循环中重复调用它会导致 O(n²) 计算复杂度。我幼稚的实现很难处理 10,000 个任务。我的机器上的开销将近 10 秒。我希望该方法几乎与内置 Task.WhenAll 一样高效,可以轻松处理数十万个任务。如何改进WhenEach 方法以使其表现得体?
【问题讨论】:
-
也许这对你有一些用处:devblogs.microsoft.com/pfxteam/… 大约在文章的中途你会看到一个性能版本。
-
@JohanP 有趣的文章,谢谢!分而治之的技术(在子序列中应用
Task.WhenAny)作为可能的解决方案在我脑海中浮现,但它很复杂,可能仍然不是最佳的。ContinueWith的另一种技术似乎更有希望,但我很难想象它如何与IAsyncEnumerable结合作为返回值。 -
不幸的是,您将无法在匿名方法中屈服,因此我无法确定 ContinueWith。
-
-
@TheGeneral 是的,我想不出用
ContinueWith方法来超越这个限制的方法。
标签: c# async-await task-parallel-library c#-8.0 iasyncenumerable