C# 在 foreach 循环中发出异步 http 请求

Posted

技术标签:

【中文标题】C# 在 foreach 循环中发出异步 http 请求【英文标题】:C# Make async http requests within a foreach loop 【发布时间】:2020-02-18 19:27:00 【问题描述】:

我需要发出多个 Web 请求,其中 URI 位于 DataTable 中。早些时候我有下面的代码。但我意识到这会进行同步调用,因为 await 会等到 GET/POST 调用完成并处理响应,然后继续进行下一次迭代。

foreach (DataRow dr in dt.Rows)

    activeTasks.Add(SendRequestAsync(dr));
    Task.WhenAll(activeTasks).Wait();


private async Task<string> SendRequestAsync(DataRow dr)

    using (var client = new HttpClient())
    
        string reqMethod = (dr["RequestMethod"] != null && dr["RequestMethod"].ToString() != "") ? dr["RequestMethod"].ToString() : "GET";
        client.BaseAddress = new Uri(dr["URL"].ToString());
        client.DefaultRequestHeaders.Accept.Clear();
        string reqContentType = (dr["RequestContentType"] != null && dr["RequestContentType"].ToString() != "") ? dr["RequestContentType"].ToString() : "text/xml";
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(reqContentType));

        HttpResponseMessage response = null;
        try
        
            if (reqMethod == "GET")
                response = await client.GetAsync(client.BaseAddress.AbsoluteUri);
            else
                response = await client.PostAsync(client.BaseAddress.AbsoluteUri, null);

            response.EnsureSuccessStatusCode();
            var responseText = await response.Content.ReadAsStringAsync();
            return responseText;
        
        catch (Exception e)
        
            return "-1";
        
    

然后我遇到了 Parallel 功能并改用了 Parallel.ForEach。像这样:

Parallel.ForEach(rows, dr =>

    activeTasks.Add(SendRequestAsync(dr));
    Task.WhenAll(activeTasks).Wait();
);

这工作正常,实现了并行性,请求是异步的,与早期的解决方案相比,它在很短的时间内完成。 但问题是它不可靠 - 有时我会遇到类似

的错误 System.IndexOutOfRangeException:索引超出数组范围 System.InvalidOperationException:集合已修改;枚举操作可能无法执行。

我们是否可以在 foreach 中实现 http 异步调用?

【问题讨论】:

我不会为此使用Parallel.ForEach。实际上,您只是想将您的 Task.WhenAll 移到您将任务添加到任务列表的循环之外。 使用并行 foreach 时,您要确保使用来自并发集合命名空间 docs.microsoft.com/en-us/dotnet/api/… 的线程安全集合 顺便说一句,如果您决定同时拨打电话,您可能需要增加每次通话的超时时间,因为您的请求可能会被同时打开的最大套接字数阻塞一些环境。 来自documentation:HttpClient 旨在被实例化一次并在应用程序的整个生命周期中重复使用。为每个请求实例化一个 HttpClient 类将耗尽重负载下可用的套接字数量。 感谢大家的回复,我给大家投票了:) 【参考方案1】:

正如@Johnathon_Chase 所说,只需将您的WhenAll() 调用移出循环即可:

foreach (DataRow dr in dt.Rows)

    activeTasks.Add(SendRequestAsync(dr));

Task.WhenAll(activeTasks).Wait();

for 循环填充集合,然后在请求完成时 Task.WhenAll() 阻塞。

【讨论】:

【参考方案2】:

Parallel.ForEach 用于 CPU 密集型操作,不是为 I/O 密集型操作或异步设计的。

您可以在 foreach 循环中使用 await 就好了。不过,包含循环的方法本身需要是异步的。

【讨论】:

以上是关于C# 在 foreach 循环中发出异步 http 请求的主要内容,如果未能解决你的问题,请参考以下文章

@foreach 循环中的多种形式。如何使用 javascript 异步提交一个。 C# 核心剃刀

node.js:如何在 forEach 循环中使用异步调用实现路由方法?

带有异步 lambda 的并行 foreach

foreach循环中的多线程C#

在 foreach 循环 Blazor 中触发网络请求

C# 发出异步的Get请求