Нужно ли удалять HttpClient и HttpClientHandler?


11 257

11 ответов:

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

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

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

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

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

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

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

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

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

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

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

Смотрите также: IDisposable.Метод Dispose и когда звонить Dispose

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();
}

Если вы не установили время простоя infinite тогда кажется безопасным не вызывать dispose и позволить таймеру простоя соединения включиться и закрыть соединения для вас, хотя было бы лучше для вас вызвать dispose в операторе using, если вы знаете, что вы закончили с экземпляром HttpClient и быстрее освободите ресурсы.

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

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

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

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

в типичном использовании (ответы

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

  • если Вы читаете данные в байт [] (например, 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, переданного ему. Причины, объясненные Ohad Schneider ответ ниже. Плохое поведение при изменении базового url.
  • HttpClientHandle позволяет издеваться в тестах

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

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

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

вы можете увидеть рабочий пример в соответствующем ответе на https://stackoverflow.com/a/50238944/3140853

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

ответ: оба следующих утверждения верны и достижимы одновременно:

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

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

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

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

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

однако, хотя это, безусловно, выглядит как недолговечная вещь, когда мы пишем код в этом стиле:

using (var foo = new SomeDisposableObject())
{
    ...
}

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

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

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

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;