多个await的执行性能与Task.WhenAll相比较。

9

概述

我希望提高一个程序向同一外部API端点发出多个HTTP请求的性能。因此,我创建了一个控制台应用程序来执行一些测试。方法GetPostAsync发送异步HTTP请求到外部API,并将结果作为字符串返回。

private static async Task<string> GetPostAsync(int id)
{
    var client = new HttpClient();
    var response = await client.GetAsync($"https://jsonplaceholder.typicode.com/posts/{id}");
    return await response.Content.ReadAsStringAsync();
}

另外,我已经实现了以下方法来比较多次调用 awaitTask.WhenAll 的执行时间。

private static async Task TaskWhenAll(IEnumerable<int> postIds)
{   
    var tasks = postIds.Select(GetPostAsync);
    await Task.WhenAll(tasks);
}

private static async Task MultipleAwait(IEnumerable<int> postIds)
{
    foreach (var postId in postIds)
    {
        await GetPostAsync(postId);
    }
}

测试结果

使用集成的Stopwatch类,我测量了这两种方法的时间,有趣的是,使用Task.WhenAll的方法比另一种方法表现得更好:

发出50个HTTP请求

  1. TaskWhenAll:约650毫秒
  2. MultipleAwait:约4500毫秒

为什么使用Task.WhenAll方法会快很多,选择这种方法是否存在任何负面效应(例如异常处理)?


5
使用foreach循环时,你会一个接一个地发送请求。这意味着每个请求必须等待前面的请求完成才能开始。而WhenAll模式可以一次性发送所有请求,而不是一个接一个地发送。没有任何等待。可能的负面影响是,某些API可能有限制,如果一次性发送50个请求,你可能会达到这些限制。但这取决于外部API,而不是代码本身。 - Patrick Tucci
1个回答

9
为什么使用Task.WhenAll方法会更快?
因为你没有等待GetPostAsync。每次你await client.GetAsync($"https://jsonplaceholder.typicode.com/posts/{id}");时,控制权会被返回给调用者,调用者可以发起另一个HTTP请求。考虑到HTTP请求比创建新客户端的时间要长得多,你可以通过同时运行多个HTTP请求来实现并行性。而WhenAll只会创建一个悬挂点并等待所有任务完成。
在多个等待的方法中,你通过foreach循环依次顺序地进行HTTP请求,即await GetPostAsync(postId)。你启动任务,但同时也会造成悬挂点并等待它完成。
这种方法是否存在负面影响(例如异常处理等)?
不存在负面影响,使用await/async模式处理异常就像使用try-catch块一样正常。WhenAll将聚合处于Faulted状态的所有任务的异常。

1
WhenAll has nothing to do with it. His second method with the foreach is running the tasks one at a time. The tasks are already running before he gets to the WhenAll - user585968
1
@MickyD 我没有说WhenAll会启动任务。我更多地考虑了方法1和方法2...我会澄清的。 - Johnny
1
这个答案是不正确的。使用WhenAll等待热任务与在循环中单独等待它们是等价的,虽然存在一些开销但是非常小。此外,错误处理有所不同。await WhenAll仅抛出一个错误,这是await的设计行为。另一方面,循环中的单独等待会捕获所有错误。 - Theodor Zoulias
@TheodorZoulias, 不幸的是(或者幸运的是),它们并不相等。在循环中等待相当于在按照先前的顺序依次触发下一个任务的等待序列(循环中也是一样)。这并不相同于一次性请求所有任务。当然,会有同步部分按顺序执行,但是从异步点开始,它们是并行运行的。 - Dmytro Mukalov
1
@Dmytro Mukalov 我在谈论的是热门任务,换句话说,就是已经开始的任务。在这种情况下,无论你是单独等待它们还是一次性等待它们都没有关系。我不是在谈论OP提供的代码,因为他的代码并不会一次性创建所有任务。它们是一个接一个地创建,并在同一个循环中等待。在这种情况下,任务确实是按顺序运行的。 - Theodor Zoulias
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接