HttpClient 异步请求失败

Posted

技术标签:

【中文标题】HttpClient 异步请求失败【英文标题】:HttpClient async requests failing 【发布时间】:2020-05-01 17:40:20 【问题描述】:

我需要从大约 3000 个网址中获取内容。我正在使用HttpClient,为每个网址创建Task,将任务添加到列表,然后await Task.WhenAll。像这样的

    var tasks = new List<Task<string>>();
    foreach (var url in urls) 
        var task = Task.Run(() => httpClient.GetStringAsync(url));
        tasks.Add(task);
    

    var t = Task.WhenAll(tasks);

但是,许多任务最终都处于FaultedCanceled 状态。我认为具体的网址可能有问题,但没有。我可以使用 curl 并行获取这些 url。

我尝试了HttpClientHandlerWinHttpHandler 和各种超时等。总是有几百个网址以错误结尾。 然后我尝试以 10 个为一组获取这些 url,这很有效。没有错误,但很慢。 Curl 将非常快地并行获取 3000 个 url。 然后我尝试获取httpbin.org 3000 次以验证问题不在于我的特定网址:

    var handler = new HttpClientHandler()  MaxConnectionsPerServer = 5000 ;
    var httpClient = new HttpClient(handler);

    var tasks = new List<Task<HttpResponseMessage>>();
    foreach (var _ in Enumerable.Range(1, 3000)) 
        var task = Task.Run(() => httpClient.GetAsync("http://httpbin.org"));
        tasks.Add(task);
    

    var t = Task.WhenAll(tasks);
    try  await t.ConfigureAwait(false);  catch  

    int ok = 0, faulted = 0, cancelled = 0;

    foreach (var task in tasks) 
        switch (task.Status) 
            case TaskStatus.RanToCompletion: ok++; break;
            case TaskStatus.Faulted: faulted++; break;
            case TaskStatus.Canceled: cancelled++; break;

        
    

    Console.WriteLine($"RanToCompletion: ok Faulted: faulted Canceled: cancelled");

同样,数百个任务总是以错误结束。

那么,这里的问题是什么?为什么我无法使用async 获取这些网址?

我正在使用 .NET Core,因此使用 ServicePointManager (Trying to run multiple HTTP requests in parallel, but being limited by Windows (registry)) 的建议不适用。

另外,我需要获取的 url 指向不同的主机。 httpbin 的代码只是一个测试,表明问题不在于我的 url 无效。

【问题讨论】:

你为什么将httpClient.GetStringAsync(url) 包裹在Task.Run 中?它已经给了你一个Task&lt;string&gt;。此外,几乎同时启动 所有 这些请求,我实际上预计有些会出现故障/超时。我会尝试使用Parallel.ForEach 来更好地控制并行性。 我猜Task.WhenAll() 在任何任务抛出异常时都会失败。尝试将 httpClient.GetAsync() 包装在 try 块中 另请注意,一次用 3000 个请求轰炸一个 url(或至少 非常 短间隔)可以让你被洪水禁止或节流最少。 附带说明一下,用 3000 个请求轰炸 httpbin.org,并发布邀请每个人都这样做的代码,可能会导致糟糕站点的托管成本增加,并且可以被视为DDoS attack 的温和形式。所以我个人不会尝试验证 OP 的实验。 在“真实”场景中,3000 个 URL 是否命中同一主机?如果是(或者即使不是),同时发起 3000 个请求是不可取的。考虑一次限制为 50 或 100 个。 Here 您会发现使用 SemphorSlim 和 TPL Dataflow 执行此操作的出色示例。 【参考方案1】:

正如 Fildor 在 cmets 中所说,httpClient.GetStringAsync 返回 Task。所以你不需要把它包装在Task.Run中。

我在控制台应用程序中运行了这段代码。完成需要 50 秒。在您的评论中,您写道 curl 在不到一分钟的时间内执行了 3000 个查询——同样的事情。

var httpClient = new HttpClient();
var tasks = new List<Task<string>>();
var sw = Stopwatch.StartNew();

for (int i = 0; i < 3000; i++)

    var task = httpClient.GetStringAsync("http://httpbin.org");
    tasks.Add(task);


Task.WaitAll(tasks.ToArray());
sw.Stop();

Console.WriteLine(sw.Elapsed);
Console.WriteLine(tasks.All(t => t.IsCompleted));

此外,所有请求均已成功完成。

在您的代码中,您正在等待使用Task.Run 启动的任务。但是需要等待调用httpClient.Get...启动的任务完成

【讨论】:

我不认为Task.Run 是问题所在。当与异步委托一起使用时,此方法会为创建的Task 创建一个瘦包装器(代理)。在这种情况下,它没有任何好处,但也不应该是有害的(除了会损害代码的可读性)。

以上是关于HttpClient 异步请求失败的主要内容,如果未能解决你的问题,请参考以下文章

Java通过httpClient同步异步发送请求

c#:如何使用 httpclient 发布异步请求并获取流?

使用 Windows.Web.Http.HttpClient 类 PATCH 异步请求

如何确定执行期间来自异步 httpclient 的并发请求数?

(办公)访问其他系统接口httpClient,异步访问

异步httpclient(httpasyncclient)的使用与总结