Нужно ли утилизировать HttpClient и HttpClientHandler?
System.Net.Http.HttpClient и System.Net.Http.HttpClientHandler в.NET Framework 4.5 реализуют IDisposable (через System.Net.Http.HttpMessageInvoker).
using
В справочной документации говорится:
Как правило, когда вы используете объект IDisposable, вы должны объявить и создать его экземпляр в операторе using.
Этот ответ использует этот шаблон:
var baseAddress = new Uri("http://example.com");
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("foo", "bar"),
new KeyValuePair<string, string>("baz", "bazinga"),
});
cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
var result = client.PostAsync("/test", content).Result;
result.EnsureSuccessStatusCode();
}
Но наиболее заметные примеры от Microsoft не вызывают Dispose()
либо явно, либо неявно. Например:
- Оригинальная статья в блоге, объявляющая о возвращении HttpClient.
- Актуальная документация MSDN для HttpClient.
- BingTranslateSample
- GoogleMapsSample
- WorldBankSample
В комментариях к объявлению кто-то спросил сотрудника Microsoft:
После проверки ваших образцов я увидел, что вы не выполняли действие dispose на экземпляре HttpClient. Я использовал все экземпляры HttpClient с оператором using в своем приложении, и я подумал, что это правильный путь, поскольку HttpClient реализует интерфейс IDisposable. Я на правильном пути?
Его ответ был:
В общем, это правильно, хотя вы должны быть осторожны с "использованием" и асинхронностью, поскольку они не смешиваются в.Net 4, в.Net 4.5 вы можете использовать "ожидание" внутри выражения "использование".
Кстати, вы можете повторно использовать один и тот же HttpClient столько раз, сколько вам нравится, поэтому обычно вы не будете создавать / распоряжаться ими все время.
Второй абзац лишний для этого вопроса, который касается не того, сколько раз вы можете использовать экземпляр HttpClient, а того, нужно ли его утилизировать после того, как он вам больше не нужен.
(Обновление: фактически, второй абзац является ключом к ответу, как указано ниже @DPeden.)
Итак, мои вопросы:
Необходимо ли, учитывая текущую реализацию (.NET Framework 4.5), вызывать Dispose() для экземпляров HttpClient и HttpClientHandler? Пояснение: под "необходимым" я подразумеваю, есть ли какие-либо негативные последствия для отказа от утилизации, такие как утечка ресурсов или риски повреждения данных.
Если в этом нет необходимости, будет ли это хорошей практикой, так как они реализуют IDisposable?
Если это необходимо (или рекомендуется), этот код, упомянутый выше, реализует его безопасно (для.NET Framework 4.5)?
Если эти классы не требуют вызова Dispose(), почему они были реализованы как IDisposable?
Если они требуют, или если это рекомендуемая практика, примеры Microsoft вводят в заблуждение или небезопасны?
11 ответов
По общему мнению, вам не нужно (не нужно) избавляться от HttpClient.
Многие люди, которые глубоко вовлечены в то, как это работает, заявили об этом.
См. Сообщение в блоге Даррела Миллера и связанный с ним пост SO: сканирование HttpClient приводит к утечке памяти для справки.
Я также настоятельно рекомендую прочитать главу HttpClient из раздела Разработка веб-API Evolvable с ASP.NET, чтобы узнать о происходящем под капотом, в частности, раздел "Жизненный цикл", приведенный здесь:
Хотя HttpClient косвенно реализует интерфейс IDisposable, стандартное использование HttpClient заключается в том, чтобы не утилизировать его после каждого запроса. Объект HttpClient предназначен для жизни до тех пор, пока ваше приложение должно делать HTTP-запросы. Наличие объекта в нескольких запросах дает возможность для установки DefaultRequestHeaders и предотвращает необходимость повторного указания таких вещей, как CredentialCache и CookieContainer, при каждом запросе, как это было необходимо для HttpWebRequest.
Или даже открыть DotPeek.
Текущие ответы немного сбивают с толку и вводят в заблуждение, и они пропускают некоторые важные последствия DNS. Я постараюсь подвести итог, где все четко.
- Вообще говоря, большинство
IDisposable
В идеале объекты должны быть удалены, когда вы закончите с ними, особенно те, которые владеют именованными / общими ресурсами ОС.HttpClient
Не исключение, поскольку, как указывает Даррел Миллер, он выделяет токены отмены, а тела запросов / ответов могут быть неуправляемыми потоками. - Тем не менее, лучшая практика для HttpClient гласит, что вы должны создать один экземпляр и максимально использовать его (используя его многопоточные элементы в многопоточных сценариях). Поэтому в большинстве сценариев вы никогда не избавитесь от него просто потому, что он будет вам нужен постоянно.
- Проблема повторного использования одного и того же HttpClient "навсегда" заключается в том, что базовое HTTP-соединение может оставаться открытым по отношению к изначально разрешенному DNS-IP, независимо от изменений DNS. Это может быть проблемой в сценариях, таких как сине-зеленое развертывание и аварийное переключение на основе DNS. Существуют различные подходы к решению этой проблемы, самый надежный из которых заключается в том, что сервер отправляет
Connection:close
заголовок после изменения DNS. Другая возможность включает в себя переработкуHttpClient
на стороне клиента, либо периодически, либо через какой-то механизм, который узнает об изменении DNS. См. https://github.com/dotnet/corefx/issues/11224 для получения дополнительной информации (я предлагаю внимательно прочитать ее, прежде чем вслепую использовать код, предложенный в связанном сообщении в блоге).
Поскольку кажется, что кто-то еще не упомянул об этом здесь, новый лучший способ управления HttpClient и HttpClientHandler в.Net Core 2.1 - это использование HttpClientFactory.
Он решает большинство вышеупомянутых проблем и ошибок простым и простым в использовании способом. Из замечательного сообщения в блоге Стива Гордона:
Добавьте следующие пакеты в свой проект.Net Core (2.1.1 или новее):
Microsoft.AspNetCore.All
Microsoft.Extensions.Http
Добавьте это в Startup.cs:
services.AddHttpClient();
Ввести и использовать:
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly IHttpClientFactory _httpClientFactory;
public ValuesController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[HttpGet]
public async Task<ActionResult> Get()
{
var client = _httpClientFactory.CreateClient();
var result = await client.GetStringAsync("http://www.google.com");
return Ok(result);
}
}
Изучите серию сообщений в блоге Стива, чтобы узнать о множестве других функций.
В моем понимании зовет Dispose()
необходим только тогда, когда он блокирует ресурсы, которые вам понадобятся позже (например, для конкретного соединения). Всегда рекомендуется освобождать ресурсы, которые вы больше не используете, даже если они вам больше не нужны, просто потому, что вы вообще не должны держаться за ресурсы, которые не используете (каламбур).
Пример Microsoft не является неправильным, обязательно. Все используемые ресурсы будут освобождены при выходе из приложения. И в случае этого примера это происходит почти сразу после HttpClient
сделано в использовании. В подобных случаях явно Dispose()
является несколько лишним.
Но, в общем, когда класс реализует IDisposable
понимание, что вы должны Dispose()
его экземпляров, как только вы полностью готовы и способны. Я бы сказал, что это особенно верно в таких случаях, как HttpClient
где явно не задокументировано, удерживаются ли ресурсы или соединения на / открыты. В случае, если соединение будет повторно использовано [скоро], вы захотите отказаться Dipose()
В этом случае вы не "полностью готовы".
См. Также: IDisposable.Dispose метод и когда вызывать Dispose
Краткий ответ: Нет, утверждение в принятом в настоящее время ответе НЕ является точным: "Общее мнение заключается в том, что вам не нужно (не нужно) избавляться от HttpClient".
Длинный ответ: ОБА из следующих утверждений верны и достижимы одновременно:
- "HttpClient предназначен для единовременного создания экземпляра и повторного использования в течение всего жизненного цикла приложения", цитируется в официальной документации.
IDisposable
объект предполагается / рекомендуется утилизировать.
И они НЕ ОБЯЗАТЕЛЬНО конфликтуют друг с другом. Это просто вопрос того, как вы организуете свой код для повторного использования HttpClient
И все равно утилизируйте его правильно.
Еще более длинный ответ процитирован из моего другого ответа:
Это не совпадение, что люди в некоторых постах блога обвиняют, как HttpClient
"s IDisposable
интерфейс заставляет их стремиться использовать using (var client = new HttpClient()) {...}
шаблон, а затем привести к исчерпанной проблеме обработчика сокета.
Я полагаю, что это сводится к негласной (ошибочной?) Концепции: "ожидаемый объект ID будет недолговечным".
ОДНАКО, хотя при написании кода в этом стиле это, безусловно, выглядит недолгим:
using (var foo = new SomeDisposableObject())
{
...
}
Официальная документация по IDisposable никогда не упоминается IDisposable
объекты должны быть недолговечными. По определению, IDisposable - это просто механизм, позволяющий вам высвобождать неуправляемые ресурсы. Ничего более. В этом смысле вы ОЖИДАЕТЕ в конечном итоге инициировать утилизацию, но это не требует, чтобы вы делали это недолгим образом.
Поэтому ваша задача - правильно выбрать, когда инициировать утилизацию, исходя из требований жизненного цикла вашего реального объекта. Ничто не мешает вам использовать IDisposable в течение длительного времени:
using System;
namespace HelloWorld
{
class Hello
{
static void Main()
{
Console.WriteLine("Hello World!");
using (var client = new HttpClient())
{
for (...) { ... } // A really long loop
// Or you may even somehow start a daemon here
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
С этим новым пониманием, теперь мы возвращаемся к этому сообщению в блоге, мы можем ясно заметить, что "исправление" инициализирует HttpClient
один раз, но никогда не удаляйте его, поэтому мы можем видеть из его вывода netstat, что соединение остается в состоянии ESTABLISHED, что означает, что оно НЕ было должным образом закрыто. Если бы он был закрыт, его состояние было бы в TIME_WAIT. На практике нет ничего страшного в том, чтобы утратить только одно открытое соединение после завершения всей вашей программы, и постер блога все еще видит увеличение производительности после исправления; но все же, концептуально неправильно обвинять IDisposable и выбирать НЕ распоряжаться им.
Dispose() вызывает приведенный ниже код, который закрывает соединения, открытые экземпляром HttpClient. Код был создан декомпиляцией с помощью dotPeek.
HttpClientHandler.cs - избавиться
ServicePointManager.CloseConnectionGroups(this.connectionGroupName);
Если вы не вызываете dispose, то ServicePointManager.MaxServicePointIdleTime, запускаемый таймером, закроет http-соединения. По умолчанию это 100 секунд.
ServicePointManager.cs
internal static readonly TimerThread.Callback s_IdleServicePointTimeoutDelegate = new TimerThread.Callback(ServicePointManager.IdleServicePointTimeoutCallback);
private static volatile TimerThread.Queue s_ServicePointIdlingQueue = TimerThread.GetOrCreateQueue(100000);
private static void IdleServicePointTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context)
{
ServicePoint servicePoint = (ServicePoint) context;
if (Logging.On)
Logging.PrintInfo(Logging.Web, SR.GetString("net_log_closed_idle", (object) "ServicePoint", (object) servicePoint.GetHashCode()));
lock (ServicePointManager.s_ServicePointTable)
ServicePointManager.s_ServicePointTable.Remove((object) servicePoint.LookupString);
servicePoint.ReleaseAllConnectionGroups();
}
Если вы не установили время простоя равным бесконечности, тогда кажется безопасным не вызывать утилиту и позволить таймеру бездействующего соединения запускать и закрывать соединения для вас, хотя было бы лучше, чтобы вы вызывали dispose в операторе using, если вы знаете, что вы сделали с экземпляром HttpClient и быстрее высвободите ресурсы.
В моем случае я создавал HttpClient внутри метода, который фактически выполнял вызов службы. Что-то вроде:
public void DoServiceCall() {
var client = new HttpClient();
await client.PostAsync();
}
В рабочей роли Azure после многократного вызова этого метода (без удаления HttpClient) он в конечном итоге завершится с ошибкой SocketException
(попытка подключения не удалась).
Я сделал HttpClient переменной экземпляра (расположив ее на уровне класса), и проблема исчезла. Поэтому я бы сказал, да, утилизируйте HttpClient, предполагая, что это безопасно (у вас нет выдающихся асинхронных вызовов).
При обычном использовании (ответы<2 ГБ) нет необходимости утилизировать сообщения HttpResponseMessages.
Типы возврата методов HttpClient должны быть уничтожены, если их потоковое содержимое не полностью прочитано. В противном случае CLR не сможет узнать, что эти потоки могут быть закрыты до тех пор, пока они не будут удалены.
- Если вы читаете данные в byte[] (например, GetByteArrayAsync) или строку, все данные читаются, поэтому нет необходимости их утилизировать.
- Другие перегрузки будут по умолчанию читать поток до 2 ГБ (HttpCompletionOption - это ResponseContentRead, HttpClient.MaxResponseContentBufferSize по умолчанию - 2 ГБ)
Если для HttpCompletionOption задано значение ResponseHeadersRead или размер ответа превышает 2 ГБ, следует выполнить очистку. Это можно сделать, вызвав Dispose для HttpResponseMessage или вызвав Dispose/Close для потока, полученного из содержимого HttpResonseMessage, или полностью прочитав его.
Вызываете ли вы Dispose на HttpClient, зависит от того, хотите ли вы отменить ожидающие запросы или нет.
Если вы хотите избавиться от HttpClient, вы можете, если вы настроите его как пул ресурсов. И в конце вашего приложения вы распределяете свой пул ресурсов.
Код:
// Notice that IDisposable is not implemented here!
public interface HttpClientHandle
{
HttpRequestHeaders DefaultRequestHeaders { get; }
Uri BaseAddress { get; set; }
// ...
// All the other methods from peeking at HttpClient
}
public class HttpClientHander : HttpClient, HttpClientHandle, IDisposable
{
public static ConditionalWeakTable<Uri, HttpClientHander> _httpClientsPool;
public static HashSet<Uri> _uris;
static HttpClientHander()
{
_httpClientsPool = new ConditionalWeakTable<Uri, HttpClientHander>();
_uris = new HashSet<Uri>();
SetupGlobalPoolFinalizer();
}
private DateTime _delayFinalization = DateTime.MinValue;
private bool _isDisposed = false;
public static HttpClientHandle GetHttpClientHandle(Uri baseUrl)
{
HttpClientHander httpClient = _httpClientsPool.GetOrCreateValue(baseUrl);
_uris.Add(baseUrl);
httpClient._delayFinalization = DateTime.MinValue;
httpClient.BaseAddress = baseUrl;
return httpClient;
}
void IDisposable.Dispose()
{
_isDisposed = true;
GC.SuppressFinalize(this);
base.Dispose();
}
~HttpClientHander()
{
if (_delayFinalization == DateTime.MinValue)
_delayFinalization = DateTime.UtcNow;
if (DateTime.UtcNow.Subtract(_delayFinalization) < base.Timeout)
GC.ReRegisterForFinalize(this);
}
private static void SetupGlobalPoolFinalizer()
{
AppDomain.CurrentDomain.ProcessExit +=
(sender, eventArgs) => { FinalizeGlobalPool(); };
}
private static void FinalizeGlobalPool()
{
foreach (var key in _uris)
{
HttpClientHander value = null;
if (_httpClientsPool.TryGetValue(key, out value))
try { value.Dispose(); } catch { }
}
_uris.Clear();
_httpClientsPool = null;
}
}
var handler = HttpClientHander.GetHttpClientHandle (новый Uri("базовый URL")).
- HttpClient, как интерфейс, не может вызвать Dispose ().
- Dispose () будет вызываться с задержкой сборщиком мусора. Или когда программа очищает объект через его деструктор.
- Использует слабые ссылки + отложенную логику очистки, поэтому она остается в использовании до тех пор, пока она часто используется повторно.
- Он только выделяет новый HttpClient для каждого базового URL, переданного ему. Причины объяснил Охад Шнайдер ответ ниже. Плохое поведение при изменении базового URL.
- HttpClientHandle позволяет Mocking в тестах
Использование внедрения зависимостей в вашем конструкторе позволяет управлять временем жизни вашего HttpClient
проще - вытащить управляющий ресурс за пределы кода, который в нем нуждается, и сделать его легко изменяемым на более позднем этапе.
В настоящее время я предпочитаю создать отдельный клиентский класс http, который наследуется от HttpClient
один раз на целевой домен конечной точки, а затем сделать его одиночным с помощью внедрения зависимостей. public class ExampleHttpClient : HttpClient { ... }
Затем я беру зависимость конструктора от пользовательского http-клиента в классах обслуживания, где мне нужен доступ к этому API. Это решает проблему срока службы и имеет преимущества, когда речь идет о пуле соединений.
Вы можете увидеть обработанный пример в соответствующем ответе на /questions/45265479/vnedrenie-edinstvennogo-ekzemplyara-httpclient-s-opredelennyim-httpmessagehandler/45265536#45265536
Не нужно вызывать Dispose, поскольку HttpClient наследует класс HttpMessageInvoker, а HttpMessageInvoker реализует интерфейс IDisposal, а HttpClientHandler наследует класс HttpMessageHandler, а HttpMessageHandler реализует интерфейс IDisposal
Пожалуйста, прочтите мой ответ на очень похожий вопрос, размещенный ниже. Должно быть ясно, что вам следует лечитьHttpClient
экземпляры как одиночные и повторно используются в запросах.
Каковы накладные расходы на создание нового HttpClient на вызов в клиенте WebAPI?
Я думаю, что нужно использовать шаблон синглтона, чтобы избежать необходимости создавать экземпляры HttpClient и постоянно его закрывать. Если вы используете.Net 4.0, вы можете использовать пример кода, как показано ниже. Для получения дополнительной информации о синглтон-паттерне смотрите здесь
class HttpClientSingletonWrapper : HttpClient
{
private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper());
public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }}
private HttpClientSingletonWrapper()
{
}
}
Используйте код, как показано ниже.
var client = HttpClientSingletonWrapper.Instance;