C# HttpClient Параллельные запросы

Верно ли, что при выполнении нескольких запросов из разных потоков HttpClient создаёт одновременно несколько подключений к сайту (или нескольким сайтам) и выполняет все запросы параллельно не зависимо друг от друга? В целях оптимизации после выполнения запроса HttpClient на некоторое время оставляет соединение открытым на случай если к тому же сайту будут выполняться ещё запросы. В случае параллельных запросов это актуально? То есть если мы выполняет одновременно запрос к сайту А и к сайту Б, и через некоторое время мы повторяем запросы, соединения не будут заново созданы. Если всё происходит не так, решением может быть создание некоторого пула клиентов для каждого домена (или для одного).


Ответы (1 шт):

Автор решения: aepot

Немного о том, как это работает.

Начнём с того, что HttpClient сам ничего не создаёт и вообще никакой сетевой активности не производит. Он является наследником некого класса HttpMessageInvoker то есть он создаёт и принимает сообщения, а непосредственно сетевую активность производит класс HttpClientHandler, который либо явно передается в конструктор клиенту, либо неявно там внутри создаётся.

Сам класс HttpClientHandler это родитель, в реальности работает его наследник, а какой именно - зависит от версии .NET и платформы и типа операционной системы. Для современной Windows и современного .NET это SocketsHttpHandler. То есть чтобы найти ответ на ваш вопрос, нужно изучать поведение именно у конкретного Handler а не у Client, а ведут хэндлеры они себя по-разному.

Когда вызываете конструктор по умолчанию, например просто new HttpClient(); на системе и версии дотнета, указанной выше, то он под капотом создаёт SocketsHttpHandler, который при новом запросе к серверу создаст новое подключение. Если вы создадите ещё один HttpClient, то произойдёт ровно то же самое, даже если запросы к одному и тому же серверу будут выполняться последовательно. Если же вы создадите один HttpClient и вы выполните 2 последовательных запроса используя один и тот же клиент, то будет переиспользовано уже открытое к серверу подключение. Если в многопоточной среде будет слаться параллельно много запросов одновременно, то подключений будет установлено больше по потребностям, чтобы обеспечить максимально быстрый результат. По умолчанию до максимум 10 одновременных подключений, запросы сверх лимита будут ожидать, когда обработаются предыдущие запросы и освободятся подключения.

Но всё вышесказанное справедливо только для протокола HTTP/1.1. По одному подключению HTTP/1.1 может одновременно выполняться только один запрос. Что касается HTTP/2, то там поддерживается мультиплексирование и в нормальных условиях все запросы, в том числе параллельные идут через одно и то же подключение. Единственное, что справедливо здесь из рассказанного выше - это то что у двух разных HttpClient будут свои подключения, а подключения друг друга они не смогут переиспользовать.

Поэтому если приложение клиентское - достаточно одного HttpClient на всё время работы приложения, независимо от используемой версии HTTP. Если приложение серверное и ему нужно слать огромное число запросов в разные стороны, то почитайте что-нибудь про реализации и использование IHttpClientFactory.

Подводя итог, для десктопного приложения оптимально:

  • один клиент
  • HTTP/2
  • желательно включить компрессию трафика

А делается это так:

private static readonly HttpClient client = new(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.All })
{
    DefaultRequestVersion = HttpVersion.Version20
};

И всё, никаких пулов не надо, один единственный клиент с этими настройками будет лупить трафик изо всех сил и сам оптимизировать расход ресурсов сети. С клиентом можно работать многопоточно. В плане отправки и приема трафика клиент является потокобезопасным.

→ Ссылка