WhenAny vs WhenAll vs WaitAll vs none,假设结果是立即使用的

Nis*_*ant 8 c# asynchronous task task-parallel-library async-await

我必须在多个异步任务完成后立即使用它们的输出。

这些方法中的任何一种都会有合理的性能差异吗?

简单的等待

public async Task<List<Baz>> MyFunctionAsync(List<Foo> FooList) {
    results = new List<Baz>();
    List<Task<List<Baz>>> tasks = new List<Task<List<Baz>>>();

    foreach (Foo foo in FooList) {
        tasks.Add(FetchBazListFromFoo(entry));

    foreach (Task<List<Baz>> task in tasks) {
        results.AddRange(await task);

    return results;
}
Run Code Online (Sandbox Code Playgroud)

当全部

public async Task<List<Baz>> MyFunctionAsync(List<Foo> FooList) {
    results = new List<Baz>();
    List<Task<List<Baz>>> tasks = new List<Task<List<Baz>>>();

    foreach (Foo foo in FooList) {
        tasks.Add(FetchBazListFromFoo(entry));

    foreach (List<Baz> bazList in await Task.WhenAll(tasks))
        results.AddRange(bazList);

    return results;
}
Run Code Online (Sandbox Code Playgroud)

等待所有

public async Task<List<Baz>> MyFunctionAsync(List<Foo> FooList) {
    results = new List<Baz>();
    List<Task<List<Baz>>> tasks = new List<Task<List<Baz>>>();

    foreach (Foo foo in FooList) {
        tasks.Add(FetchBazListFromFoo(entry));

    foreach (List<Baz> bazList in await Task.WaitAll(tasks))
        results.AddRange(bazList);

    return results;
}
Run Code Online (Sandbox Code Playgroud)

当任何时候

public async Task<List<Baz>> MyFunctionAsync(List<Foo> FooList) {
    results = new List<Baz>();
    List<Task<List<Baz>>> tasks = new List<Task<List<Baz>>>();

    foreach (Foo foo in FooList) {
        tasks.Add(FetchBazListFromFoo(entry));

    while (tasks.Count > 0) {
        Task<List<Baz>> finished = Task.WhenAny(tasks);
        results.AddRange(await finished);
        tasks.Remove(finished);
    }

    return results;
}
Run Code Online (Sandbox Code Playgroud)
  • FooList有大约 100 个条目。
  • FetchBazListFromFoo进行大约 30 个 REST API 调用,并对 REST API 调用的每个结果执行一些同步工作。

此外,WhenAll 与 WhenAny 之间是否存在内部开销差异?

WhenAll 在所有任务完成后返回控制权,而 WhenAny 在单个任务完成后立即返回控制权。后者似乎需要更多的内部管理。

The*_*ias 8

第三种方法(WaitAll)无效,因为它Task.WaitAll是一个void返回方法,所以不能等待。这段代码只会产生编译时错误。

其他三种方法非常相似,但有一些细微的差别。

简单等待:启动所有任务,然后逐一等待它们。它将按正确的顺序收集所有结果。如果发生异常,它将在所有任务完成之前返回,并且仅报告第一个失败任务的异常(按顺序排列,而不是按时间顺序)。
不建议这样做,除非这种行为正是您想要的(很可能不是)。

WhenAll:启动所有任务,然后等待所有任务完成。它将按正确的顺序收集所有结果。如果出现异常,它将在所有任务完成后返回,并且仅报告第一个失败任务的异常(按顺序排列,而不是按时间顺序)。
不建议这样做,除非这种行为正是您想要的(很可能也不是)。

WhenAny:启动所有任务,然后等待所有任务完成。它将按完成顺序收集所有结果,因此原始顺序将不会保留。如果出现异常,它会立即返回,并且会报告第一个失败任务的异常(这次首先按时间顺序,而不是按顺序)。该while循环引入了其他两种方法所没有的开销,如果任务数量大于 10,000,该开销将非常显着,并且随着任务数量的增加,开销将呈指数级增长。
不推荐,除非这种行为正是您想要的(我敢打赌,现在您也不喜欢这种行为)。

所有这些方法:都会用大量并发请求轰炸远程服务器,使该机器难以快速响应,并且在最坏的情况下触发防御性抗 DOS 攻击机制。

解决此问题的更好方法是使用Parallel.ForEachAsync.NET 6 及更高版本中提供的专用 API。该方法并行化多个异步操作,强制执行最大并行度(默认为 )Environment.ProcessorCount,并且还支持异常情况下的取消和快速完成。您可以在此处找到使用示例。该方法不返回异步操作的结果。您可以收集结果作为异步操作的副作用,如此处此处所示


Tyl*_*ley 2

简单的等待将基本上同步地一个接一个地执行每一项——这将是最慢的。

WhenAll将等待所有任务完成 - 运行时间将是最长的单个任务。

不要使用WaitAll- 它是同步的,只需使用WhenAll

WhenAny允许您在每项任务完成时对其进行处理。这将比WhenAll某些情况下更快,具体取决于任务完成后需要执行的处理量。

IMO,除非您需要在每个任务完成时立即开始后处理,否则WhenAll这是最简单/最干净的方法,并且在大多数情况下都可以正常工作。