在Parallel.Foreach中使用HttpClient进行大量POST请求的正确方法

时间:2019-06-18 22:40:45

标签: .net-core async-await dotnet-httpclient parallel.foreach

您可能会在下面的代码中看到,在较高的层次上,该代码以递归方式读取文件夹结构,并将其内容发布到API。应用程序是.Net core 2.1。

我有这项服务,可以对API进行POST。

    public class EnterpriseService
    {
        private readonly HttpClient _httpClient;

        public EnterpriseService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<string> PostTransactionAsync(byte[] payload)
        {
            using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri("https://www.foo.com/api/transaction")))
            {
                request.Content = new ByteArrayContent(payload);
                HttpResponseMessage response = await _httpClient.SendAsync(request);
                return await response.Content.ReadAsStringAsync();
            }
        }
    }

PostTransactionAsync通过以下方式被调用方调用:

        protected async Task SearchFoldersAsync(List<FileStatusProperties> folders, string root, CancellationToken cancellationToken)
        {
            await Task.Run(() =>
            {
                return Parallel.ForEach(folders, async entry =>
                {
                    if (entry.Type == FileType.DIRECTORY)
                    {
                        await SearchFoldersAsync(
                            DataLakeStorage.DirectoryGetFiles($"{root}/{entry.PathSuffix}"),
                            $"{root}/{entry.PathSuffix}", cancellationToken);
                        return;
                    }

                    byte[] payload = DataLakeStorage.FileDownload($"{root}/{entry.PathSuffix}");
                    await _enterpriseService.PostTransactionAsync(payload);

                });
            }, cancellationToken);

        }

请注意,我使用的是直接作为单例的HttpClient。

我还递归使用Parallel.Foreach。

此代码非常适合包含10K +文件的较小文件夹结构。但是,当文件数量增加时(例如,文件夹中的文件数量达到了约10万个),我会混合使用这2个错误。大约20%的请求成功。 _httpClient.SendAsync调用中的40%的请求最终都遇到这两个异常。请求在10秒后失败。

  

每个套接字地址(协议/网络地址/端口)仅一种用法   通常是允许的

  

操作被取消。无法从传输中读取数据   连接:由于以下原因之一,I / O操作已被中止:   线程退出或应用程序请求。 I / O操作已   由于线程退出或应用程序请求而中止

我了解了HttpClient的用法,据我所知,我没有做错任何事情。但是我不确定它是否与递归Parallel.ForEach一起使用。

我想知道在需要同时进行大量http请求的情况下处理此情况的推荐方法是什么?

2 个答案:

答案 0 :(得分:1)

Parallel用于并行性,它是一种并发形式,它使用多个线程在多个内核之间分配CPU限制的工作。您想要的是异步并发,这是同时执行多个I / O绑定操作的更合适的方法。

最容易完成异步并发的方法是,为每个项目启动Task(通常使用Select),然后对所有这些任务执行await Task.WhenAll。像这样:

protected async Task SearchFoldersAsync(List<FileStatusProperties> folders, string root, CancellationToken cancellationToken)
{
  var tasks = folders.Select(async entry =>
  {
    if (entry.Type == FileType.DIRECTORY)
    {
      await SearchFoldersAsync(
          DataLakeStorage.DirectoryGetFiles($"{root}/{entry.PathSuffix}"),
          $"{root}/{entry.PathSuffix}", cancellationToken);
      return;
    }

    byte[] payload = DataLakeStorage.FileDownload($"{root}/{entry.PathSuffix}");
    await _enterpriseService.PostTransactionAsync(payload);
  }).ToList();
  await Task.WhenAll(tasks);
}

答案 1 :(得分:0)

我不会处理Parallel vs async ...

但是这个特定的错误

  

每个套接字地址(协议/网络地址/端口)通常只允许使用一种

似乎是由于从一个系统到另一个系统上的单个端口只能有大约65k的连接。

假设现有服务器进程正在使用端口80,则可以启动使用其他端口的其他进程。但是,您需要多个1 HttpClient,并且需要在它们之间进行轮询或其他操作。进程太多,您可能开始达到客户端或服务器上打开文件描述符的限制。