Czy HttpClient i HttpClientHandler muszą zostać usunięte?

System. Net. Http.HttpClient i System. Net. Http. HttpClientHandler W. NET Framework 4.5 zaimplementować IDisposable (poprzez System.Net.Http. HttpMessageInvoker).

Dokumentacja deklaracji using mówi:

Z reguły, gdy używasz obiektu IDisposable, powinieneś zadeklarować i Utwórz instancję w instrukcji using.

Ta odpowiedź używa tego wzoru:

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

Ale najbardziej widoczne przykłady z Microsoftu nie wywołaj Dispose() ani jawnie, ani niejawnie. Na przykład:

W komentarzu ktoś zapytał pracownika Microsoftu:

Po sprawdzeniu próbek, Widziałem, że nie wykonałeś działanie na instancji HttpClient. Użyłem wszystkich instancji HttpClient z użyciem oświadczenia w mojej aplikacji i pomyślałem, że jest to właściwy sposób ponieważ HttpClient implementuje interfejs IDisposable. Czy jestem na dobra ścieżka?

Jego odpowiedź brzmiała:

Ogólnie jest to poprawne, chociaż trzeba być ostrożnym z "using" i asynchroniczne jak nie ' naprawdę mieszać w. Net 4, w. Net 4.5 ty można użyć "oczekuj" wewnątrz " używając" oświadczenie.

Btw, możesz ponownie użyć tego samego HttpClient tyle razy, ile chcesz zazwyczaj nie będziesz ich cały czas tworzyć/usuwać.

Drugi akapit jest zbędny w tym pytaniu, które nie dotyczy tego, ile razy można użyć instancji HttpClient, ale tego, czy konieczne jest pozbycie się jej po tym, jak już jej nie potrzebujesz.

(Aktualizacja: w rzeczywistości drugi akapit jest kluczem do odpowiedzi, jak podano poniżej przez @DPeden.)

Więc moje pytania to:

  1. Czy w obecnej implementacji (. NET Framework 4.5) konieczne jest wywołanie metody Dispose () na instancjach HttpClient i HttpClientHandler? Wyjaśnienie: przez "konieczne" mam na myśli, czy istnieją jakiekolwiek negatywne konsekwencje nie zbycia, takie jak ryzyko wycieku zasobów lub uszkodzenia danych.

  2. Jeśli nie jest to konieczne, czy i tak byłaby to "dobra praktyka", skoro wdrażają IDisposable?

  3. Jeśli jest to konieczne (lub zalecane), czy wspomniany wyżej kod implementuje go bezpiecznie (dla. NET Framework 4.5)?

  4. Jeśli te klasy nie wymagają wywołania Disposable (), dlaczego zostały zaimplementowane jako IDisposable?

  5. Czy przykłady Microsoftu wprowadzają w błąd lub są niebezpieczne, jeśli tego wymagają lub jest to zalecana praktyka?

Author: robbie fan, 2013-03-29

11 answers

Ogólny konsensus jest taki, że nie musisz (nie powinieneś) pozbywać się HttpClient.

Wiele osób, które są intymnie zaangażowane w sposób, w jaki działa, stwierdziło to.

Zobacz Darrel Miller ' s blog post i powiązane so post: HttpClient crawling wyniki w wycieku pamięci w celach informacyjnych.

Sugerowałbym również, abyś przeczytał Rozdział HttpClient z projektowanie ewolucyjnych interfejsów API internetowych z ASP.NET dla kontekstu co się dzieje pod maską, szczególnie w sekcji" cykl życia " przytoczonej tutaj:

Chociaż HttpClient pośrednio implementuje IDisposable interfejsu, standardowym użyciem HttpClient nie jest pozbycie się go po każdej prośbie. Obiekt HttpClient jest przeznaczony do życia jako tak długo, jak aplikacja musi składać żądania HTTP. Posiadanie przedmiotu istnieje wiele żądań umożliwia ustawienie DefaultRequestHeaders i zapobiega konieczności ponownego określenia rzeczy jak CredentialCache i CookieContainer na każde życzenie jako było konieczne z HttpWebRequest.

Lub nawet otworzyć DotPeek.

 201
Author: David Peden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:35

Obecne odpowiedzi są nieco mylące i mylące, i brakuje im ważnych implikacji DNS. Postaram się podsumować, gdzie rzeczy stoją jasno.

  1. Ogólnie rzecz biorąc większość IDisposable obiektów powinna być idealnie usunięta, gdy skończysz z nimi, zwłaszcza te, które posiadają nazwane/udostępnione zasoby systemu operacyjnego. HttpClient nie jest wyjątkiem, ponieważ jak wskazuje Darrel Miller przydziela tokeny anulowania, a organy żądania / odpowiedzi mogą być niezarządzane strumienie.
  2. jednak najlepsza praktyka dla HttpClient mówi, że należy utworzyć jedną instancję i używać jej w jak największym stopniu (używając jej członków thread-safe w scenariuszach wielowątkowych). Dlatego w większości scenariuszy nigdy nie pozbędziesz się go po prostu dlatego, że będziesz go potrzebował cały czas .
  3. problem z ponownym użyciem tego samego HttpClient "na zawsze" polega na tym, że podstawowe połączenie HTTP może pozostać otwarte w stosunku do oryginalnego DNS-rozwiązany adres IP, niezależnie od zmian DNS . Może to być problem w scenariuszach, takich jak wdrażanie niebieski/zielony i przełączanie awaryjne oparte na DNS. Istnieją różne sposoby radzenia sobie z tym problemem, najbardziej niezawodne z nich polega na wysyłaniu przez serwer nagłówka Connection:close po wprowadzeniu zmian DNS. Inną możliwością jest recykling HttpClient po stronie klienta, okresowo lub za pomocą jakiegoś mechanizmu, który dowiaduje się o zmianie DNS. Zobacz też https://github.com/dotnet/corefx/issues/11224 aby uzyskać więcej informacji (sugeruję przeczytanie go uważnie przed ślepym użyciem kodu sugerowanego w linkowanym poście na blogu).
 21
Author: Ohad Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-08 10:21:21

W moim rozumieniu wywołanie Dispose() jest konieczne tylko wtedy, gdy blokuje zasoby, których potrzebujesz później(jak konkretne połączenie). Jest to zawsze zalecane aby uwolnić zasoby, których już nie używasz, nawet jeśli nie potrzebujesz ich ponownie, po prostu dlatego, że nie powinieneś ogólnie trzymać się zasobów, których nie używasz (gra słów zamierzona).

Przykład Microsoftu niekoniecznie jest błędny. Wszystkie użyte zasoby zostaną zwolnione po zakończeniu działania aplikacji. Oraz w przykład ten ma miejsce niemal natychmiast po użyciu HttpClient. W podobnych przypadkach jawne wywołanie Dispose() jest nieco zbędne.

Ale, ogólnie rzecz biorąc, kiedy klasa implementuje IDisposable, zrozumienie jest takie, że powinieneś Dispose() jej instancji, gdy tylko będziesz w pełni gotowy i zdolny. Chciałbym założyć, że jest to szczególnie prawdziwe w przypadkach takich jak HttpClient, w których nie jest wyraźnie udokumentowane, czy zasoby lub połączenia są utrzymywane/otwarte. W przypadku gdy połączenie zostanie ponownie wykorzystane [wkrótce], będziesz chciał zrezygnować Dipose() z niego - nie jesteś "w pełni gotowy" w tym przypadku.

[[7]}Zobacz: IDisposable.Metoda Dispose I kiedy wywołać Dispose
 16
Author: svidgen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-10 15:31:11

Dispose() wywołuje poniższy kod, który zamyka połączenia otwarte przez instancję HttpClient. Kod został stworzony przez dekompilację za pomocą dotPeek.

HttpClientHandler.cs-Dispose

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

Jeśli nie wywołasz dispose, to ServicePointManager.MaxServicePointIdleTime, który jest uruchamiany przez timer, zamknie połączenia http. Wartość domyślna to 100 sekund.

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

Jeśli nie ustawiłeś czasu bezczynności na nieskończony, to wydaje się bezpieczne, aby nie wywołaj dispose i pozwól, aby timer bezczynnego połączenia uruchomił i zamknął połączenia za ciebie, chociaż byłoby lepiej, abyś wywołał dispose w instrukcji using, jeśli wiesz, że skończyłeś z instancją HttpClient i szybciej zwolnij zasoby.

 7
Author: Timothy Gonzalez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-26 22:49:51

W moim przypadku tworzyłem HttpClient wewnątrz metody, która faktycznie wykonała wywołanie usługi. Coś jak:

public void DoServiceCall() {
  var client = new HttpClient();
  await client.PostAsync();
}
W roli worker usługi Azure, po wielokrotnym wywołaniu tej metody (bez usuwania HttpClient), w końcu zakończy się ona niepowodzeniem z SocketException (Próba połączenia nie powiodła się).

Zrobiłem z HttpClient zmienną instancji (usuwając ją na poziomie klasy) i problem zniknął. Więc powiedziałbym, tak, pozbyć HttpClient, zakładając, że jest bezpieczny (nie masz outstanding async calls), aby to zrobić.

 4
Author: David Faivre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-26 14:06:31

W typowym użyciu (odpowiedzi

Typy zwrotne metod HttpClient powinny zostać usunięte, jeśli ich zawartość strumienia nie jest w pełni odczytana. W przeciwnym razie nie ma możliwości, aby CLR wiedział, że strumienie mogą być zamknięte, dopóki nie zostaną zebrane śmieci.

  • jeśli wczytujesz dane do bajtu[] (np. GetByteArrayAsync) lub string, wszystkie dane są odczytywane, więc nie ma potrzeby ich usuwania.
  • inne przeciążenia domyślnie odczyta strumień do 2 GB (HttpCompletionOption to ResponseContentRead, HttpClient.MaxResponseContentBufferSize Domyślnie to 2GB)

Jeśli ustawisz HttpCompletionOption na ResponseHeadersRead lub odpowiedź jest większa niż 2GB, należy oczyścić. Można to zrobić, wywołując Dispose na Httpresonsemessage lub wywołując Dispose / Close na strumieniu uzyskanym z zawartości HttpResonseMessage lub całkowicie odczytując zawartość.

Czy dzwonisz Usuwanie za pomocą HttpClient zależy od tego, czy chcesz anulować oczekujące żądania, czy nie.

 3
Author: Tom Deseyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-26 21:40:51

Jeśli chcesz pozbyć się HttpClient, możesz, jeśli skonfigurujesz go jako pulę zasobów. A na końcu wniosku, można pozbyć się puli zasobów.

Kod:

// 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 (new Uri ("base url")).

  • HttpClient, jako interfejs, nie może wywołać metody Dispose ().
  • Dispose () zostanie wywołane z opóźnieniem przez Garbage Collector. Lub gdy program oczyści obiekt przez jego Destruktor.
  • zastosowania Słabe Referencje + opóźniona logika czyszczenia, więc pozostaje w użyciu tak długo, jak jest często używany ponownie.
  • przydziela nowy Klient HttpClient tylko dla każdego bazowego adresu URL przekazanego do niego. Powody wyjaśnione przez Ohad Schneider odpowiedź poniżej. Złe zachowanie podczas zmiany podstawowego adresu url.
  • HttpClientHandle pozwala na wyśmiewanie w testach
 0
Author: TamusJRoyce,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-27 15:37:15

Użycie dependency injection w konstruktorze sprawia, że zarządzanie czasem życia twojego HttpClient jest łatwiejsze - biorąc zarządzanie czasem życia poza kod, który go potrzebuje i czyniąc go łatwo zmienialnym w późniejszym terminie.

Obecnie preferuję utworzenie oddzielnej klasy klienta http, która dziedziczy z HttpClient raz na domenę docelowego punktu końcowego , a następnie uczyni ją singletonem za pomocą iniekcji zależności. public class ExampleHttpClient : HttpClient { ... }

Następnie biorę zależność konstruktora od niestandardowego klienta http w klasach usług, gdzie potrzebuję dostępu do tego API. Rozwiązuje to problem związany z żywotnością i ma zalety, jeśli chodzi o łączenie połączeń.

Możesz zobaczyć przykład pracy w powiązanej odpowiedzi na https://stackoverflow.com/a/50238944/3140853

 0
Author: alastairtree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-08 17:37:16

Krótka odpowiedź: Nie, stwierdzenie w aktualnie akceptowanej odpowiedzi nie jest dokładne: "ogólny konsensus jest taki, że nie musisz (nie powinieneś) pozbywać się HttpClient".

Długa odpowiedź: oba poniższe stwierdzenia są prawdziwe i możliwe do osiągnięcia w tym samym czasie:

  1. "HttpClient jest przeznaczony do utworzenia instancji raz i ponownie używany przez cały okres życia aplikacji", cytowany z oficjalnej dokumentacji.
  2. An IDisposable obiekt powinien / powinien być usuwany.

I nie muszą ze sobą kolidować. To tylko kwestia tego, jak zorganizujesz swój kod, aby ponownie użyć HttpClient i nadal usuwać go prawidłowo.

An even longer answer quoted from my another answer :

To nie przypadek widzieć ludzi w niektóre wpisy na blogu sprawia, że mają tendencję do używania wzoru using (var client = new HttpClient()) {...} a następnie doprowadzić do wyczerpanego gniazda problem z opiekunem.

Wierzę, że sprowadza się to do niewypowiedzianego (mis?) poczęcie: "oczekuje się, że obiekt IDisposable będzie krótkotrwały".

Jednak, choć z pewnością wygląda to na krótkotrwałą rzecz, gdy piszemy kod w tym stylu:

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

Oficjalna dokumentacja na IDisposable nigdy nie wspomina IDisposable obiekty muszą być krótkotrwałe. Z definicji IDisposable jest jedynie mechanizmem pozwalającym na uwolnienie niezarządzanych zasobów. Nic więcej. W tego poczucia, oczekuje się, że w końcu uruchomisz utylizację, ale to nie wymaga, aby to zrobić w sposób krótkotrwały.

Twoim zadaniem jest zatem właściwe wybranie, kiedy uruchomić utylizację, bazuj na wymaganiach dotyczących cyklu życia prawdziwego obiektu. Nic nie stoi na przeszkodzie, aby używać IDisposable w długotrwały sposób:

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();
        }
    }
}
Z tym nowym zrozumieniem, teraz wracamy do tego postu na blogu [20]}, możemy wyraźnie zauważyć, że" fix " inicjalizuje HttpClient raz, ale nigdy go nie pozbywaj, dlatego z jego wyjścia netstat widzimy, że, połączenie pozostaje w ustalonym stanie, co oznacza, że nie zostało prawidłowo zamknięte. Gdyby była zamknięta, jej stan byłby w TIME_WAIT. W praktyce nie jest to nic wielkiego przeciekać tylko jedno połączenie otwarte po zakończeniu całego programu, a plakat na blogu nadal widać wzrost wydajności po fixie; jednakowoż nie jest pojęciowo błędem winić, że jest możliwe i zdecydować, że nie będzie się go pozbywać.
 0
Author: RayLuo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-31 20:19:22

No need to call Dispose Ponieważ HttpClient dziedziczy klasę HttpMessageInvoker, a HttpMessageInvoker implementuje interfejs IDisposal i HttpClientHandler dziedziczą klasę HttpMessageHandler i httpmessagehandler implementują interfejs IDisposal

 0
Author: Sunil Dhappadhule,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-11 13:15:41

Myślę, że należy użyć singleton pattern, aby uniknąć konieczności tworzenia instancji HttpClient i zamykania go przez cały czas. Jeśli używasz. Net 4.0 możesz użyć przykładowego kodu, jak poniżej. więcej informacji na temat singleton pattern check TUTAJ .

class HttpClientSingletonWrapper : HttpClient
{
    private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper()); 

    public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }}

    private HttpClientSingletonWrapper()
    {
    }
}

Użyj kodu jak poniżej.

var client = HttpClientSingletonWrapper.Instance;
 -3
Author: yayadavid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 15:44:48