【问题标题】:Asynch and Parallel HttpClient Request to 62,000+ machines : Best Practice Request对 62,000 多台机器的异步和并行 HttpClient 请求:最佳实践请求
【发布时间】:2017-09-17 15:50:30
【问题描述】:

我有这种情况,我正在研究需要将通知消息发送到公司所有工作站的位置。我们有一个处理桌面通知的客户端服务,它是一个非常简单的 WebApi。

问题/要求 如何从服务器/ASP.Net Web 应用程序异步和并行向所有这些机器发送请求,并捕获其对自定义日志文件的响应?

在发出通知时,许多机器可能已关闭,或者由于机器可能已停用,dns 可能无法解析。

对当前正在原型化的代码的请求/响应周期需要一个非阻塞的周期。这样用户就不必等待所有这些机器的响应。

我的作业: 我了解异步操作更适合 IO 绑定操作,但是机器数量如此之多,让我感到并行和异步 在一起,可能更适合这种情况。

我已设置 ServicePointManager.DefaultConnectionLimit = 10000

HttpClient 的构造函数带有 dispose 选项为 false 已通过。请参阅下面的代码 - 工厂类。

框架中的 HttpClient 类是为重用而设计的,但是据我了解,某些属性不会像基地址一样更改。来源:HttpClient - This instance has already started

我正在使用请求/响应特定 ID(相关 ID)来确保所有异步和并行操作都针对给定请求在同一个文件夹中进行。

TaskCanceledException catch 块永远不会被命中!!

超时已延长至 30 秒,我确信这已经足够了。

原型代码:

public class HttpClientFactory  : IHttpClientFactory
    {
        public void CreateClient(string baseUrl, Action<HttpClient> methodToExecute)
        {
            using (var handler = new HttpClientHandler())
            {
                handler.AllowAutoRedirect = true;
                using (var client = new HttpClient(handler, false))
                {
                    client.BaseAddress = new Uri(baseUrl);
                    client.Timeout = TimeSpan.FromSeconds(30);
                    methodToExecute(client);
                }
            }
        }
   }

IHttpClientFactory 是瞬态的 (IoC)

workstationUrls.ForEach(baseUrl =>
            {
                _httpClientFactory.CreateClient(baseUrl, async (client) =>
                {
                    await client.PostAsync(resourceUrl, content).ContinueWith(t =>
                    {
                        try
                        {
                            var response = t.Result;

                            var workstationResponse = new WorkstationResponse
                            {
                                StatusCode = (int)response.StatusCode,
                                Response = response.Content.ReadAsStringAsync().Result
                            };

                            workstationResponse.IsSuccess
                                = workstationResponse.StatusCode >= 200 &&
                                  workstationResponse.StatusCode <= 299
                                    ? true
                                    : false;

                            var docContent = JsonConvert.SerializeObject(workstationResponse);
                            if (workstationResponse.IsSuccess)
                            {
                                // Write workstation log
                                File.WriteAllText(path
                                                  + "\\Bulk Notifications\\"
                                                  + userFolder + "\\Success\\"
                                                  + GetWorkstationNameFromUrl(baseUrl)
                                                  + GetUniqueTimeStampForFileNames()
                                                  + workstationResponse.IsSuccess + ".txt",
                                    docContent);
                            }
                            else
                            {
                                // Write workstation log
                                File.WriteAllText(path
                                                  + "\\Bulk Notifications\\"
                                                  + userFolder + "\\Fail\\"
                                                  + GetWorkstationNameFromUrl(baseUrl)
                                                  + GetUniqueTimeStampForFileNames()
                                                  + workstationResponse.IsSuccess + ".txt",
                                    docContent);
                            }

                        }
                        catch (TaskCanceledException exe)
                        {

                        }
                        catch (Exception ex)
                        {

                            var workstationResponse = new WorkstationResponse
                            {
                                Exception = ex,
                                IsSuccess = false
                            };
                            var docContent = JsonConvert.SerializeObject(workstationResponse);
                            // Write workstation log
                            File.WriteAllText(path
                                              + "\\Bulk Notifications\\"
                                              + userFolder + "\\Fail\\"
                                              + GetWorkstationNameFromUrl(baseUrl) + " "
                                              + GetUniqueTimeStampForFileNames() + " "
                                              + workstationResponse.IsSuccess + ".txt", docContent);
                        }
                    });
                });
            }); 

原型代码问题: 我在异常消息中收到这些错误,而 StackTrace 似乎没有帮助

  • “任务已取消”
  • “无法访问已释放的对象。对象名称:'System.Net.Http.StringContent'”

【问题讨论】:

  • 首先将操作 Action&lt;HttpClient&gt; methodToExecute 更改为返回 Task 的函数,以便您可以等待与 HttpClient 正确交互的异步方法。这导致了已处理的异常。
  • 谢谢彼得,让我试试看是否会有所作为。

标签: c# asp.net asp.net-web-api httpclient dotnet-httpclient


【解决方案1】:

首先,异步和并行不是相互排斥的;不同之处在于您使用的构造以及与“传统”并行性不同的是,您不会在每个异步操作中消耗/阻塞线程。

并发执行异步操作并等待(异步)所有操作完成的最基本构造是Task.WhenAll。我首先定义一个异步方法来向单个工作站发送消息:

async Task SendMessageToWorkstationAsync(string url)

(可以很容易地从您的代码中派生实现。)

然后这样称呼它:

await Task.WhenAll(workstationUrls.Select(SendMessageToWorkstationAsync));

其次,关于HttpClient,如果您因为设置BaseAddress 而没有重用单个实例,解决方案很简单:不要那样做。 :) 这不是必需的。跳过工厂类,只创建一个共享的 HttpClient 实例,并为每个请求提供完整的 URI。

最后,根据各种因素,您可能仍然会发现 62,000 多个并发请求超出了系统的处理能力。如果是这种情况,您将需要限制并行度。我发现最好的方法是使用 TPL 数据流。有关如何执行此操作的详细信息,请参阅this question

【讨论】:

  • 感谢托德的洞察力。让我试试看能不能进步。
猜你喜欢
  • 2018-02-22
  • 1970-01-01
  • 2021-05-07
  • 1970-01-01
  • 2020-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多