Я использую .Net 7.0
У меня есть коллекция System.Threading. Поток, выполняющий различные веб-запросы.
Потоки совместно используют сетевую абстракцию, использующую HttpClient.
В этой абстракции Я асинхронно получаю ответ с помощью GetAsync(url), а затем содержимое с помощью ReadAsStringAsync().
Метод GetAsync(url) генерирует исключение HttpRequestException. с сообщением об ошибке Ошибка при копировании содержимого в поток до 5 % случаев.
Иллюстрация кода
Я не могу поделиться реальным кодом, но если это поможет, вот соответствующие части, упрощенные и очищенные.
Код: Выделить всё
interface INetworkAbstraction
{
Task GetContent(string url);
}
class NetworkImplementation : INetworkAbstraction
{
HttpClient _clientA;
HttpClient _clientB;
public NetworkHelper()
{
_clientA = new HttpClient();
_clientB = new HttpClient();
// configure the different HttpClients
}
private HttpClient GetRelevantHttpClient(string url)
{
// some HttpClient selection logic
return true ? _clientA : _clientB;
}
public async Task GetContent(string url)
{
try
{
// some url validation stuffs
using HttpResponseMessage response = await GetRelevantHttpClient(url).GetAsync(url);
using HttpContent content = response.Content;
return await content.ReadAsStringAsync();
}
catch (Exception ex)
{
// catch logic
}
}
}
class SomeType
{
INetworkAbstraction _networkAbstraction;
public SomeType(INetworkAbstraction networkAbstraction)
{
_networkAbstraction = networkAbstraction;
}
public async Task DoStuffs(string url)
{
// do stuffs
string result = await _networkAbstraction.GetContent(url);
// do other stuffs
}
}
class SomeLayerInTheCodeBase
{
INetworkAbstraction _networkAbstraction;
static object _resultLock = new object();
async Task GetSomeTypes(IEnumerable urls)
{
List threads = new List();
List result = new List();
foreach(var url in urls)
{
Thread thread = new Thread(async () =>
{
var businessObject = new SomeType(_networkAbstraction);
await businessObject.DoStuffs(url);
lock (_resultLock)
result.Add(businessObject);
});
thread.Start();
threads.Add(thread);
}
while (threads.Any(t => t.IsAlive))
await Task.Delay(100);
return result;
}
}
Перед использованием System.Threading.Threads , я использовал параллельную библиотеку, и она работала нормально.
Нагрузка на процессор была низкой, так как большая часть времени уходила на ожидание ответа, а параллельная библиотека не масштабировалась более чем количество ядер процессора. (более или менее, есть и другие факторы, но в моем случае, когда для MaxDegreeOfParallelism установлено максимальное значение, оно масштабируется до количества ядер процессора).
Кроме того, сеть не была узким местом.
Я пытался использовать DataFlow, но без лучших результатов, чем с параллельной библиотекой.
С помощью System.Threading .Thread, я могу увеличить количество одновременных запросов, и, согласно моим измерениям, общее время сократится на 75 %, что является весьма значительным улучшением, поскольку мне нужно выполнять тысячи запросов на получение.
На данный момент основным узким местом является пропускная способность моей сети.
Диагностика
Учитывая, что использование классические потоки создали проблему, и я получаю исключение для общего объекта, думаю, я ввел состояние гонки.
Код: Выделить всё
HttpClientПоэтому я могу изменять внутреннее состояние общего HttpClient при вызове GetAsync(url) , что приводит к состоянию гонки.
Я пытался проверить это, просмотрев реализацию GetAsync(url), но не смог подтвердить.
Текущий обходной путь
Использование мьютекса перед выполнением запроса сведет на нет преимущества многопоточности и невозможно. Вы не можете вызывать асинхронные методы в блокировке по веским причинам. Это не имело бы особого смысла и приводило к ошибкам.
Я нашел решение, запрашивая новый экземпляр HttpClient для каждого запроса.
Проблема в том, что сокет не освобождается, когда HttpClient удаляется до определенного периода ожидания, что приводит к ситуации исчерпания сокета.
Чтобы избежать этого , я делюсь приведенным ниже и соответствующим SocketsHttpHandler и указываю конструктору HttpClient, чтобы он не удалял предоставленный экземпляр SocketHttpHandler.
Теперь я не удаляю Я больше не получаю HttpRequestException, и, согласно netstat, я не злоупотребляю клиентскими сокетами, несмотря на тысячи запросов.
Кроме того, я не измеряю существенного влияния на производительность относительно создания экземпляра константы HttpClient.
Мои вопросы
Поделитесь ли вы моей диагностикой? Или я упустил известную проблему в описанном контексте?
Каково ваше мнение о текущем решении?
Знаете ли вы лучшее решение, которое решит проблему и сохранит производительность?
Подробнее здесь: https://stackoverflow.com/questions/787 ... ementation