Асинхронный и параллельный запрос HttpClient на 62000+ машин: запрос на передовой опыт

У меня есть этот сценарий, я работаю над тем, где нужно отправить уведомление на все рабочие станции в моей компании. У нас есть клиентская служба, которая обрабатывает уведомления на рабочем столе, и это очень простой WebApi.

Вопрос / требование Как мне одновременно и в асинхронном, и в параллельном режиме отправить запрос на все эти машины из веб-приложения сервера / ASP.Net и записать его ответ в настраиваемый файл журнала?

Многие машины могут быть выключены во время отправки уведомления, или DNS может быть не в состоянии разрешить, поскольку машина могла быть выведена из эксплуатации.

Цикл запроса / ответа на код, который в настоящее время прототипируется, должен быть неблокирующим. Так что пользователю не придется ждать ответа от всех этих машин.

Моя домашняя работа: Я понимаю, что асинхронные операции лучше подходят для операций с привязкой к вводу-выводу, однако количество машин настолько велико, что я чувствую параллельность и асинхронность вместе, может лучше подойти для этой ситуации.

Я установил ServicePointManager.DefaultConnectionLimit = 10000

HttpClient с конструктором с параметром dispose равным false был передан. См. Ниже код - Заводской класс.

Класс HttpClient в структуре предназначен для повторного использования, однако, насколько я понимаю, некоторые свойства не могут быть изменены, например, базовый адрес. Источник: HttpClient - этот экземпляр уже запущен

Я использую конкретный идентификатор запроса / ответа (идентификатор корреляции), чтобы убедиться, что все асинхронные и параллельные операции выполняются в одной и той же папке для данного запроса.

Блок перехвата TaskCanceledException никогда не срабатывает !!

Таймаут увеличен до 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'»

person RaM    schedule 17.09.2017    source источник
comment
Начните с изменения действия Action<HttpClient> methodToExecute на функцию, которая возвращает задачу, чтобы вы могли дождаться асинхронных методов, которые правильно взаимодействуют с HttpClient. Это вызывает удаленное исключение.   -  person Peter Bons    schedule 17.09.2017
comment
Thans Peter, позвольте мне попробовать и посмотреть, имеет ли это значение.   -  person RaM    schedule 18.09.2017


Ответы (1)


Во-первых, async и parallel не исключают друг друга; разница заключается в используемых конструкциях и в том факте, что, в отличие от «традиционного» параллелизма, вы не потребляете / не блокируете поток для каждой асинхронной операции.

Самая простая конструкция для одновременного выполнения асинхронных операций и ожидания (асинхронно) их завершения - Task.WhenAll. Я бы начал с определения асинхронного метода отправки сообщения на одиночную рабочую станцию:

async Task SendMessageToWorkstationAsync(string url)

(Реализацию довольно легко получить из вашего кода.)

Тогда назовите это так:

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

Во-вторых, что касается HttpClient, если вы не используете повторно ни один экземпляр, потому что вы устанавливаете BaseAddress, решение простое: не делайте этого. :) Не требуется. Пропустите фабричный класс и просто создайте вместо этого один общий экземпляр HttpClient и предоставьте полный URI для каждого запроса.

Наконец, в зависимости от множества факторов, вы все равно можете обнаружить, что 62 000+ одновременных запросов - это больше, чем система может обработать. Если это так, вам нужно ограничить параллелизм. Я нашел лучший способ сделать это с помощью TPL Dataflow. См. этот вопрос для получения подробной информации о том, как это сделать.

person Todd Menier    schedule 17.09.2017
comment
Спасибо todd за понимание. Позвольте мне попробовать и посмотреть, смогу ли я добиться прогресса. - person RaM; 18.09.2017