Asynch和Parallel HttpClient请求62,000多台机器:最佳实践请求

时间:2017-09-17 15:50:30

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

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

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

在发出通知时可能会关闭许多机器,或者由于机器可能已经退役,因此dns可能无法解决。

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

我的家庭作业: 我理解异步操作更适合IO绑定操作,但是机器数量太多使我感觉并行和异步  在一起,可能更适合这种情况。

我已设置ServicePointManager.DefaultConnectionLimit = 10000

已经传递了带有dispose选项为false的构造函数的HttpClient。见下面的代码 - 工厂类。

框架中的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是Transient(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);
                        }
                    });
                });
            }); 

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

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

1 个答案:

答案 0 :(得分:3)

首先,异步和并行不是互斥的;不同之处在于您使用的构造以及与传统&#34;不同的事实。并行性,你不会为每个异步操作消耗/阻塞一个线程。

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

async Task SendMessageToWorkstationAsync(string url)

(可以从代码中轻松派生实现。)

然后这样称呼:

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

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

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