У меня есть от 10 до 150 объектов живого класса, которые вызывают методы, выполняющие простые вызовы API HTTPS с использованием HttpClient. Пример вызова PUT:
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.UseCookies = true;
handler.CookieContainer = _Cookies;
using (HttpClient client = new HttpClient(handler, true))
{
client.Timeout = new TimeSpan(0, 0, (int)(SettingsData.Values.ProxyTimeout * 1.5));
client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Statics.UserAgent);
try
{
using (StringContent sData = new StringContent(data, Encoding.UTF8, contentType))
using (HttpResponseMessage response = await client.PutAsync(url, sData))
{
using (var content = response.Content)
{
ret = await content.ReadAsStringAsync();
}
}
}
catch (ThreadAbortException)
{
throw;
}
catch (Exception ex)
{
LastErrorText = ex.Message;
}
}
}
Через 2-3 часа после запуска этих методов, которые включают правильное удаление с помощью операторов using
, программа ползла до 1 ГБ-1,5 ГБ памяти и, в конечном счете, с ошибками памяти. Много раз соединения осуществляются через ненадежные прокси-серверы, поэтому соединения могут не выполняться, как ожидалось (таймауты и другие ошибки являются общими).
.NET Memory Profiler указал, что HttpClientHandler
является основной проблемой здесь, заявив, что он имеет как "Disposed экземпляры с прямыми корнями делегата" (красный восклицательный знак), так и "Экземпляры, которые были удалены, но все еще не GCed" ( желтый восклицательный знак). Делегаты, о которых указывает профайлер, были укоренены AsyncCallback
s, вытекающие из HttpWebRequest.
Он также может относиться к RemoteCertValidationCallback
, что-то относящееся к проверке сертификата HTTPS, поскольку TlsStream
- это объект, расположенный ниже в корне, который является "Disposed, но не GCed".
С учетом всего этого - как я могу более правильно использовать HttpClient и избежать этих проблем с памятью? Должен ли я форсировать GC.Collect()
каждый час или около того? Я знаю, что это считается плохой практикой, но я не знаю, как еще вернуть эту память, которая не совсем правильно утилизируется, и лучшая модель использования этих короткоживущих объектов не очевидна для меня, как кажется быть недостатком в самих объектах .NET.
UPDATE
Принуждение GC.Collect()
не имело эффекта.
Всего управляемые байты для процесса остаются согласованными примерно на 20-30 МБ, в то время как общая память процесса (в диспетчере задач) продолжает расти, указывая на неуправляемую утечку памяти. Таким образом, этот шаблон использования создает неуправляемую утечку памяти.
Я попытался создать экземпляры уровня класса как HttpClient, так и HttpClientHandler по предложению, но это не оказало заметного эффекта. Даже когда я устанавливаю их на уровень класса, они все еще воссоздаются и редко используются повторно из-за того, что параметры прокси-сервера часто требуют изменения. HttpClientHandler не позволяет изменять настройки прокси-сервера или какие-либо свойства после того, как запрос был инициирован, поэтому я постоянно пересобираю обработчик, как это было первоначально сделано с независимыми операторами using
.
HttpClienthandler все еще используется с "прямыми корнями делегата" в AsyncCallback → HttpWebRequest. Я начинаю удивляться, может быть, HttpClient просто не был предназначен для быстрых запросов и объектов с коротким проживанием. Нет конца в поле зрения. В надежде, что у кого-то есть предложение сделать использование HttpClientHandler жизнеспособным.
Профайлер памяти: