Нужно ли удалять 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() явно или неявно. Например:

В комментариях к анонсу кто-то спросил сотрудника Microsoft:

После проверки ваших образцов я увидел, что вы не выполнили действие dispose на экземпляре HttpClient. Я использовал все экземпляры HttpClient с помощью инструкции в своем приложении, и я подумал, что это правильный путь, поскольку HttpClient реализует интерфейс IDisposable. Я на правильном пути?

Его ответ был:

В общем, это правильно, хотя вы должны быть осторожны с "использованием" и асинхронно, поскольку они не "смешиваются".Net 4, In.Net 4.5 вы можете использовать "ожидание" внутри инструкции "using".

Кстати, вы можете повторно использовать один и тот же HttpClient столько раз [как] вам нравится, что обычно вы не будете создавать/удалять их все время.

Второй абзац излишним в этом вопросе, который не беспокоится о том, сколько раз вы можете использовать экземпляр HttpClient, но о том, нужно ли его утилизировать после того, как он вам больше не нужен.

(Обновление: фактически, второй абзац является ключом к ответу, как описано ниже в разделе @DPeden.)

Поэтому мои вопросы:

  1. Нужно ли, учитывая текущую реализацию (.NET Framework 4.5), вызвать Dispose() в экземплярах HttpClient и HttpClientHandler? Уточнение: "обязательно" я имею в виду, если есть какие-либо негативные последствия для не утилизации, такие как утечка ресурсов или риски коррупции данных.

  2. Если это не обязательно, будет ли это "хорошей практикой", так как они реализуют IDisposable?

  3. Если это необходимо (или рекомендуется), является ли упомянутый выше код безопасным (для.NET Framework 4.5)?

  4. Если эти классы не требуют вызова Dispose(), почему они были реализованы как IDisposable?

  5. Если они требуют или, если это рекомендуемая практика, являются примерами Microsoft, вводящими в заблуждение или небезопасными?

Ответ 1

По общему мнению, вам не нужно (не нужно) избавляться от HttpClient.

Многие люди, которые глубоко вовлечены в то, как это работает, заявили об этом.

См. Сообщение в блоге Даррела Миллера и соответствующее сообщение SO: сканирование HttpClient приводит к утечке памяти для справки.

Я также настоятельно рекомендую прочитать главу HttpClient из раздела Разработка веб-API Evolvable с ASP.NET для контекста того, что происходит под капотом, особенно в разделе "Жизненный цикл", цитируемом здесь:

Хотя HttpClient косвенно реализует интерфейс IDisposable, стандартное использование HttpClient заключается в том, чтобы не утилизировать его после каждого запроса. Объект HttpClient предназначен для жизни до тех пор, пока ваше приложение должно делать HTTP-запросы. Наличие объекта в нескольких запросах дает возможность для установки DefaultRequestHeaders и предотвращает необходимость повторного указания таких вещей, как CredentialCache и CookieContainer, при каждом запросе, как это было необходимо для HttpWebRequest.

Или даже открыть DotPeek.

Ответ 2

Текущие ответы немного запутывают и вводят в заблуждение, и им не хватает некоторых важных последствий для DNS. Я постараюсь обобщить, где все ясно.

  • В целом большинство IDisposable объектов должны идеально располагаться, когда вы закончите с ними, особенно те, что own Named/shared Ресурсов ОС. HttpClient не является исключением, так как Darrel Miller указывает, что он выделяет токены отмены, а тела запроса/ответа могут быть неуправляемыми потоками.
  • Тем не менее, наилучшая практика для HttpClient говорит, что вы должны создать один экземпляр и использовать его как можно больше (используя потокобезопасные элементы в многопоточных сценариях). Поэтому в большинстве сценариев вы никогда не избавитесь от него просто потому, что вам понадобится все это время.
  • Проблема с повторным использованием одного и того же HttpClient "навсегда" заключается в том, что базовое HTTP-соединение может оставаться открытым против первоначально разрешенного DNS IP, независимо от DNS изменения. Это может быть проблемой в сценариях типа синего/зеленого развертывания и отказоустойчивости на основе DNS. Существуют различные подходы к решению этой проблемы, наиболее надежные с сервером, отправляющим заголовок Connection:close после изменений DNS. Другая возможность включает в себя рециркуляцию HttpClient на стороне клиента либо периодически, либо через какой-либо механизм, который узнает об изменении DNS. См. https://github.com/dotnet/corefx/issues/11224 для получения дополнительной информации (я предлагаю внимательно прочитать его, прежде чем вслепую использовать код, предложенный в связанном сообщении блога).

Ответ 3

В моем понимании вызов Dispose() необходим только тогда, когда он блокирует ресурсы, которые вам нужны позже (например, конкретное соединение). Всегда рекомендуется освобождать ресурсы, которые вы больше не используете, даже если они вам больше не нужны, просто потому, что вы обычно не держите ресурсы, которые вы не используете (каламбур).

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

Но, вообще говоря, когда класс реализует IDisposable, понимаем, что вы должны Dispose() его экземпляров, как только будете полностью готовы и способны. Я бы сказал, что это особенно верно в таких случаях, как HttpClient, где он явно не задокументирован, связаны ли ресурсы или соединения с/открытыми. В случае, когда соединение будет повторно использовано повторно [скоро], вы захотите отказаться от него - вы не будете "полностью готовы" в этом случае.

См. также: IDisposable.Dispose Method и Когда вызывать Dispose

Ответ 4

Dispose() вызывает код ниже, который закрывает соединения, открытые экземпляром HttpClient. Код был создан путем декомпиляции с помощью dotPeek.

HttpClientHandler.cs - Dispose

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 и не позволять таймеру соединения в режиме ожидания запускать и закрывать соединения для вас, хотя было бы лучше, если бы вы вызывали dispose в используя инструкцию, если вы знаете, что вы закончили с экземпляром HttpClient и быстрее освободите ресурсы.

Ответ 5

Краткий ответ: Нет, утверждение в принятом в настоящее время ответе НЕ является точным: "Общее мнение заключается в том, что вам не нужно (не нужно) избавляться от HttpClient".

Длинный ответ: ОБА из следующих утверждений верны и достижимы одновременно:

  1. "HttpClient предназначен для однократного создания экземпляра и повторного использования в течение всего жизненного цикла приложения", цитируется в официальной документации.
  2. IDisposable объект предполагается/рекомендуется утилизировать.

И они НЕ ОБЯЗАТЕЛЬНО конфликтуют друг с другом. Это просто вопрос того, как вы организуете свой код, чтобы повторно использовать HttpClient И все же правильно его утилизировать.

Еще более длинный ответ процитирован из моего другого ответа:

Не случайно, когда люди в некоторых сообщениях блога обвиняют, что интерфейс HttpClient IDisposable заставляет их склоняться к использованию using (var client = new HttpClient()) {...}, а затем приводит к исчерпанной проблеме с обработчиком сокетов.

Я полагаю, что это сводится к негласной (ошибочной?) Концепции: "ожидается, что IDisposable объект будет недолговечным".

ОДНАКО, хотя это, конечно, выглядит недолгим, когда мы пишем код в этом стиле:

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 один раз, но никогда не HttpClient его, поэтому мы можем видеть из его вывода netstat, что соединение остается в состоянии ESTABLISHED, что означает его НЕ был правильно закрыт. Если бы он был закрыт, его состояние было бы в TIME_WAIT. На практике нет ничего страшного в том, чтобы утратить только одно открытое соединение после завершения всей вашей программы, и постер блога все еще видит увеличение производительности после исправления; но все же, концептуально неправильно обвинять IDisposable и выбирать НЕ распоряжаться им.

Ответ 6

В моем случае я создавал HttpClient внутри метода, который фактически выполнял вызов службы. Что-то вроде:

public void DoServiceCall() {
  var client = new HttpClient();
  await client.PostAsync();
}

В роли рабочего Azure после неоднократного вызова этого метода (без удаления HttpClient) он в конечном итоге завершится неудачей с помощью SocketException (попытка подключения не удалась).

Я сделал HttpClient переменной экземпляра (убрав его на уровне класса), и проблема исчезла. Поэтому я бы сказал, да, удалите HttpClient, предположив, что он безопасен (у вас нет выдающихся асинхронных вызовов).

Ответ 7

В типичном использовании (ответы < 2 ГБ) нет необходимости Dispose HttpResponseMessages.

Возвращаемые типы методов HttpClient должны быть удалены, если их потоковое содержимое не полностью прочитано. В противном случае CLR не знает, что те потоки могут быть закрыты, пока не будут собраны мусор.

  • Если вы читаете данные в байт [] (например, GetByteArrayAsync) или строку, все данные считываются, поэтому нет необходимости распоряжаться.
  • Другие перегрузки по умолчанию будут считывать поток до 2 ГБ (HttpCompletionOption - это ResponseContentRead, HttpClient.MaxResponseContentBufferSize по умолчанию - 2 ГБ).

Если вы установите HttpCompletionOption в ResponseHeadersRead или ответ больше 2 ГБ, вы должны очистить его. Это можно сделать, вызвав Dispose в HttpResponseMessage или вызвав Dispose/Close в потоке, полученном из содержимого HttpResonseMessage, или полностью прочитав содержимое.

Вызов Dispose на HttpClient зависит от того, хотите ли вы отменить ожидающие запросы или нет.

Ответ 8

Поскольку, похоже, никто еще не упомянул об этом здесь, новый лучший способ управления 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);
    }
}

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

Ответ 9

Если вы хотите избавиться от 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 в тестах

Ответ 10

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

Мое настоящее предпочтение заключается в создании отдельного клиентского класса http, который наследует от HttpClient один раз на целевой конечный домен, а затем делает его одиночным, используя инъекцию зависимостей. public class ExampleHttpClient: HttpClient {... }

Затем я беру зависимость конструктора от пользовательского http-клиента в классах услуг, где мне нужен доступ к этому API. Это решает проблему продолжительности жизни и имеет преимущества, когда дело доходит до объединения пулов.

Вы можете увидеть обработанный пример в соответствующем ответе на fooobar.com/questions/13482415/...

Ответ 11

Нет необходимости вызывать Dispose. Поскольку HttpClient наследует класс HttpMessageInvoker и HttpMessageInvoker реализует интерфейс IDisposal и HttpClientHandler наследует класс HttpMessageHandler и HttpMessageHandler реализует интерфейс IDisposal

Ответ 13

Я думаю, что нужно использовать одноэлементный шаблон, чтобы избежать необходимости создавать экземпляры HttpClient и закрывать его все время. Если вы используете .Net 4.0, вы можете использовать пример кода, как показано ниже. для получения дополнительной информации о проверке шаблона Singleton здесь.

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;