Czy HttpClient i HttpClientHandler muszą być usuwane między żądaniami?


334

System.Net.Http.HttpClient i System.Net.Http.HttpClientHandler w .NET Framework 4.5 implementują IDisposable (przez System.Net.Http.HttpMessageInvoker ).

Dokumentacja usingoświadczenia mówi:

Z reguły, gdy używasz obiektu IDisposable, powinieneś zadeklarować go i utworzyć jego instancję w instrukcji using.

Ta odpowiedź wykorzystuje ten wzorzec:

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 firmy Microsoft nie wywołują Dispose()ani jawnie, ani niejawnie. Na przykład:

W zapowiedź „s komentarze, ktoś poprosił pracownika firmy Microsoft:

Po sprawdzeniu próbek zauważyłem, że nie wykonałeś operacji usuwania na instancji HttpClient. Użyłem wszystkich wystąpień HttpClient z użyciem instrukcji w mojej aplikacji i pomyślałem, że jest to właściwy sposób, ponieważ HttpClient implementuje interfejs IDisposable. Czy jestem na dobrej drodze?

Jego odpowiedź brzmiała:

Zasadniczo jest to poprawne, chociaż trzeba uważać na „używanie” i asynchronizację, ponieważ tak naprawdę nie mieszają się one w .Net 4, w .Net 4.5 można użyć „czekaj” wewnątrz instrukcji „using”.

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

Drugi akapit jest zbędny w stosunku do tego pytania, które nie martwi się, ile razy można użyć instancji HttpClient, ale o tym, czy konieczne jest usunięcie go, gdy już go nie potrzebujesz.

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

Więc moje pytania to:

  1. Czy w obecnej wersji (.NET Framework 4.5) konieczne jest wywołanie metody Dispose () w instancjach HttpClient i HttpClientHandler? Wyjaśnienie: przez „konieczne” mam na myśli, jeśli istnieją negatywne konsekwencje braku wyrzucenia, takie jak wyciek zasobów lub ryzyko uszkodzenia danych.

  2. Jeśli nie jest to konieczne, czy byłaby to „dobra praktyka”, ponieważ implementują one IDisposable?

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

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

  5. Jeśli wymagają lub jeśli jest to zalecana praktyka, czy przykłady Microsoft wprowadzają w błąd lub są niebezpieczne?


2
@Damien_The_Unbeliever, dziękuję za opinie. Czy masz jakieś sugestie dotyczące wyjaśnienia pytania? Chcę wiedzieć, czy może to prowadzić do problemów ogólnie związanych z niewykorzystywaniem zasobów, takich jak wyciek zasobów i uszkodzenie danych.
Fernando Correia

9
@Damien_The_Unbeliever: Nieprawda. W szczególności osoby zapisujące strumień muszą mieć odpowiednie zachowanie.
Stephen Cleary

1
@StephenCleary - o jakich aspektach myślisz? Z pewnością możesz zadzwonić Flushjeden po każdym zapisie, a poza niedogodnościami związanymi z utrzymywaniem podstawowych zasobów dłużej niż to konieczne, co się nie stanie, co jest wymagane do „prawidłowego zachowania”?
Damien_The_Unbeliever

1
Jest to po prostu błędne: „Z reguły, gdy używasz obiektu IDisposable, powinieneś zadeklarować i utworzyć go w instrukcji using”. Zawsze czytałem dokumentację dotyczącą klasy implementującej IDisposable, zanim zdecydowałem, czy powinienem użyć do niej użycia. Ponieważ autor bibliotek, w których implementuję IDisposable, musiałby uwolnić niezmienione zasoby, byłbym przerażony, gdyby konsumenci tworzyli instancję za każdym razem, zamiast ponownie wykorzystywać istniejącą. Nie oznacza to, że ostatecznie nie pozbądź się instancji ..
markmnl

Odpowiedzi:


259

Ogólny konsensus jest taki, że nie trzeba (nie należy) pozbywać się HttpClient.

Stwierdzało to wiele osób, które są ściśle zaangażowane w sposób, w jaki działa.

Zobacz post na blogu Darrel Miller i powiązany post SO: Przeszukiwanie HttpClient powoduje wyciek pamięci w celach informacyjnych.

Zdecydowanie sugeruję również przeczytanie rozdziału HttpClient z Projektowanie ewoluowalnych interfejsów API sieci Web za pomocą programu ASP.NET, aby uzyskać kontekst na temat tego, co dzieje się pod maską, szczególnie cytowany tutaj rozdział „Cykl życia”:

Chociaż HttpClient nie implementuje pośrednio interfejsu IDisposable, standardowym zastosowaniem HttpClient nie jest usuwanie go po każdym żądaniu. Obiekt HttpClient ma żyć tak długo, jak długo aplikacja musi wysyłać żądania HTTP. Posiadanie obiektu dla wielu żądań umożliwia miejsce na ustawienie DefaultRequestHeaders i zapobiega konieczności ponownego określania takich rzeczy, jak CredentialCache i CookieContainer dla każdego żądania, tak jak było to konieczne w przypadku HttpWebRequest.

Lub nawet otwórz DotPeek.


64
Aby wyjaśnić swoją odpowiedź, czy poprawne byłoby powiedzenie, że „nie musisz pozbywać się HttpClient, JEŚLI UTRZYMASZ SIĘ W INSTYTUCIE, ABY PONOWNIE UŻYWAĆ PÓŹNIEJ”? Na przykład, jeśli metoda jest wywoływana wielokrotnie i tworzy nową instancję HttpClient (nawet jeśli nie jest to zalecany wzorzec w większości przypadków), czy nadal poprawne byłoby stwierdzenie, że ta metoda nie powinna pozbyć się instancji (która nie zostanie ponownie użyta)? Może to prowadzić do tysięcy niedysponowanych przypadków. Innymi słowy, że powinieneś spróbować ponownie wykorzystać instancje, ale jeśli nie użyjesz ich ponownie, lepiej je zutylizuj (aby zwolnić połączenia)?
Fernando Correia,

8
Myślę, że zrozumiała frustracja, ale prawidłowa odpowiedź, zależy. Gdybym musiał zostać przyłapany na udzielaniu ogólnych porad, które działały w większości (nigdy nie mówię wszystkich), sugerowałbym, abyś użył kontenera IoC i zarejestrował instancję HttpClient jako singleton. Cykl życia instancji byłby wówczas ograniczony do okresu życia kontenera. Można to ustalić na poziomie aplikacji lub być może na żądanie w aplikacji internetowej.
David Peden

25
@FernandoCorreia Tak. Jeśli z jakiegoś powodu wielokrotnie tworzysz i niszczysz instancje HttpClient, to tak, powinieneś je usunąć. Nie sugeruję zignorowania interfejsu IDisposable, po prostu próbuję zachęcić ludzi do ponownego użycia instancji.
Darrel Miller,

20
Aby jeszcze bardziej zwiększyć wiarygodność tej odpowiedzi, rozmawiałem dziś z zespołem HttpClient i potwierdzili oni, że HttpClient nie został zaprojektowany do użycia na żądanie. Instancja HttpClient powinna być utrzymywana przy życiu, podczas gdy aplikacja kliencka będzie nadal współpracować z określonym hostem.
Darrel Miller

19
@DavidPeden Rejestracja HttpClient jako singletonu wydaje mi się niebezpieczna, ponieważ jest zmienna. Na przykład, czy wszyscy przypisujący do Timeoutnieruchomości nie będą się ze sobą tupać?
Jon-Eric,

47

Obecne odpowiedzi są nieco mylące i wprowadzające w błąd i brakuje im ważnych implikacji DNS. Spróbuję podsumować, gdzie wszystko jest jasne.

  1. Ogólnie rzecz biorąc, większość IDisposableobiektów powinna być najlepiej utylizowana, gdy z nimi skończysz , szczególnie te, które posiadają nazwane / współdzielone zasoby systemu operacyjnego . HttpClientnie jest wyjątkiem, ponieważ jak zauważa Darrel Miller , alokuje tokeny anulowania, a ciała żądania / odpowiedzi mogą być niezarządzanymi strumieniami.
  2. Jednak najlepsza praktyka dla HttpClient mówi, że powinieneś utworzyć jedną instancję i wykorzystać ją tak często, jak to możliwe (używając elementów bezpiecznych dla wątków w scenariuszach wielowątkowych). Dlatego w większości scenariuszy nigdy nie pozbędziesz się tego 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 pierwotnie rozstrzygniętego adresu IP, niezależnie od zmian DNS . Może to stanowić problem w scenariuszach takich jak wdrożenie niebiesko-zielone i przełączanie awaryjne w oparciu o DNS . Istnieją różne podejścia do rozwiązania tego problemu, z których najbardziej wiarygodny polega na tym, że serwer wysyła Connection:closenagłówek po zmianach DNS. Inna możliwość polega na recyklingu HttpClientpo stronie klienta, albo okresowo, albo poprzez jakiś mechanizm, który uczy się o zmianie DNS. Aby uzyskać więcej informacji, zobacz https://github.com/dotnet/corefx/issues/11224 (sugeruję uważne przeczytanie go przed ślepym użyciem kodu sugerowanego w linkowanym blogu).

Pozbywam się tego cały czas, ponieważ nie mogę przełączyć proxy na instancję;)
ed22

Jeśli z jakiegoś powodu musisz pozbyć się HttpClient, powinieneś zachować statyczną instancję HttpMessageHandler, ponieważ pozbywanie się tego jest przyczyną problemów związanych z pozbywaniem się HttpClient. HttpClient ma przeciążenie konstruktora, które pozwala określić, że dostarczony moduł obsługi nie powinien zostać usunięty, w takim przypadku można ponownie użyć HttpMessageHandler z innymi instancjami HttpClient.
Tom Lint

Powinieneś trzymać się swojego HttpClient, ale możesz użyć czegoś takiego jak System.Net.ServicePointManager.DnsRefreshTimeout = 3000; Jest to przydatne np. Jeśli korzystasz z urządzenia mobilnego, które w dowolnym momencie może przełączać się między Wi-Fi a 4G.
Johan Franzén

18

W moim rozumieniu, wywołanie Dispose()jest konieczne tylko wtedy, gdy blokuje zasoby, których potrzebujesz później (jak określone połączenie). Zawsze zaleca się uwolnienie zasobów, których już nie używasz, nawet jeśli nie będziesz ich ponownie potrzebować, po prostu dlatego, że zasadniczo nie powinieneś trzymać się zasobów, których nie używasz (zamierzona gra słów).

Przykład Microsoftu niekoniecznie jest niepoprawny. Wszystkie używane zasoby zostaną zwolnione po zamknięciu aplikacji. I w przypadku tego przykładu dzieje się to prawie natychmiast po zakończeniu HttpClientużywania. W podobnych przypadkach jawne wywoływanie Dispose()jest nieco zbędne.

Ale generalnie, kiedy klasa implementuje IDisposable, zrozumienie jest takie, że powinieneś Dispose()o jej wystąpieniach, gdy tylko będziesz w pełni gotowy i zdolny. Sądzę, że jest to szczególnie prawdziwe w przypadkach, w HttpClientktó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 - w tym przypadku nie jesteś „w pełni gotowy”.

Zobacz także: IDisposable.Dispose Metoda i kiedy należy wywołać Dispose


7
To tak, jakby ktoś przyniósł banana do twojego domu, zjadł go i stoi ze skórką. Co powinni zrobić ze skórką? ... Jeśli wychodzą z niej drzwiami, pozwól im odejść. Jeśli się trzymają, niech wyrzucą je do śmieci, żeby nie śmierdziały tym miejscem.
svidgen

aby wyjaśnić tę odpowiedź, czy mówisz: „nie trzeba usuwać, jeśli program zakończy się zaraz po jego użyciu”? I że powinieneś się pozbyć, jeśli program ma trwać przez jakiś czas, robiąc inne rzeczy?
Fernando Correia

@FernandoCorreia Tak, chyba że o czymś zapomnę, myślę, że to bezpieczna zasada. Zastanów się jednak nad tym w każdym przypadku. Jeśli na przykład pracujesz z połączeniem, nie chcesz Dispose()go przedwcześnie i musisz połączyć się kilka sekund później, jeśli istniejące połączenie może być ponownie użyte. Podobnie, nie chcesz niepotrzebnie Dispose()obrazów lub innych struktur, które możesz odbudować w ciągu minuty lub dwóch.
svidgen

Rozumiem. Ale w konkretnym przypadku HttpClient i HttpClientHandler, którego dotyczy to pytanie, czy utrzymują otwarty zasób, taki jak połączenie HTTP? Jeśli tak się dzieje, być może będę musiał przemyśleć swój wzorzec ich używania.
Fernando Correia

1
@DPeden Twoja odpowiedź wcale nie jest sprzeczna z moją. Uwaga, powiedziałem, powinieneś usunąć () swoje wystąpienia, gdy tylko będziesz w pełni gotowy i zdolny . Jeśli planujesz ponownie użyć instancji, nie jesteś gotowy .
svidgen

9

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 - Usuń

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

Jeśli nie wywołasz usuwania, ServicePointManager.MaxServicePointIdleTime, który działa według timera, zamknie połączenia http. Domyślna wartość 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 czas bezczynności nie został ustawiony na nieskończoność, wydaje się bezpieczne, aby nie wywoływać usuwania i pozwolić, aby licznik czasu bezczynności uruchamiał się i zamykał połączenia dla ciebie, chociaż lepiej byłoby, gdybyś zadzwonił do usuwania w instrukcji użycia, jeśli wiesz, że masz już instancję HttpClient i szybciej zwalniasz zasoby.



8

Krótka odpowiedź: Nie, stwierdzenie w obecnie akceptowanej odpowiedzi NIE JEST dokładne : „Ogólny konsensus jest taki, że nie musisz (nie powinieneś) pozbywać się HttpClient”.

Długa odpowiedź : ZARÓWNO następujące stwierdzenia są prawdziwe i możliwe do osiągnięcia w tym samym czasie:

  1. „HttpClient ma zostać utworzony raz i ponownie użyty przez cały okres użytkowania aplikacji”, cytowany z oficjalnej dokumentacji .
  2. IDisposableObiekt ma / zaleca się być utylizowane.

I NIE KONIECZNIE SĄ KONFLIKTOWANE. Jest to tylko kwestia tego, jak zorganizujesz swój kod, aby ponownie wykorzystać HttpClientORAZ nadal go poprawnie usuwać.

Jeszcze dłuższa odpowiedź cytowana z mojej innej odpowiedzi :

To nie jest przypadek, aby zobaczyć ludzi w niektórych blogach obwiniając jak HttpClient„s IDisposableinterfejs sprawia, że mają tendencję do korzystania z using (var client = new HttpClient()) {...}wzorca, a następnie doprowadzić do wyczerpania problemu handler gniazdo.

Uważam, że sprowadza się to do niewypowiedzianej (błędnej?) Koncepcji: „oczekuje się, że obiekt IDisposable będzie krótkotrwały” .

JEDNAK, choć na pewno wygląda to na coś krótkotrwałego, gdy piszemy kod w tym stylu:

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

Oficjalna dokumentacja IDisposable nie wspomina IDisposableobiekty mają być krótkotrwałe. Z definicji IDisposable jest jedynie mechanizmem umożliwiającym uwolnienie niezarządzanych zasobów. Nic więcej. W tym sensie OCZEKUJESZ, że ostatecznie doprowadzisz do usunięcia, ale nie wymaga to, abyś zrobił to w krótkim czasie.

Dlatego Twoim zadaniem jest właściwy wybór momentu uruchomienia usuwania, w oparciu o wymagania dotyczące cyklu życia Twojego obiektu. Nic nie stoi na przeszkodzie, abyś mógł używać IDisposable w długim okresie:

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

Dzięki temu nowemu zrozumieniu, teraz ponownie odwiedzamy ten post na blogu , możemy wyraźnie zauważyć, że „poprawka” inicjuje się HttpClientraz, ale nigdy go nie usuwa, dlatego z jego danych wyjściowych netstat wynika, że ​​połączenie pozostaje w stanie USTALONYM, co oznacza, że ​​ma NIE zostało poprawnie zamknięte. Gdyby był zamknięty, jego stanem byłby TIME_WAIT. W praktyce nie jest wielkim problemem wyciek tylko jednego połączenia otwartego po zakończeniu całego programu, a plakat na blogu nadal widzi wzrost wydajności po poprawce; ale obwinianie IDisposable i wybieranie NIE wyrzucania go jest koncepcyjnie niepoprawne.


dziękuję za to wyjaśnienie. Wyraźnie rzuca światło na konsensus. Z Twoim zdaniem, kiedy myślisz, że to jest właściwa zadzwonić HttpClient.Dispose?.
Jeson Martajaya

@JesonMartajaya, usuń go, gdy aplikacja nie będzie już musiała korzystać z instancji httpClient. Może ci się wydawać, że taka sugestia brzmi niejasno, ale w rzeczywistości może idealnie dopasowywać się do cyklu życia HttpClient clientzmiennej, co jest rzeczą Programowania-101, którą prawdopodobnie i tak już robisz. Możesz nawet nadal móc z niego korzystać using (...) {...}. Na przykład zobacz przykład Hello World w mojej odpowiedzi.
RayLuo

7

Ponieważ nie wydaje się, aby ktokolwiek tutaj o tym wspominał, nowym najlepszym sposobem zarządzania HttpClient i HttpClientHandler w .Net Core 2.1 jest użycie HttpClientFactory .

Rozwiązuje większość wyżej wymienionych problemów i problemów w czysty i łatwy w użyciu sposób. Ze wspaniałego postu na blogu Steve'a Gordona :

Dodaj następujące pakiety do projektu .Net Core (2.1.1 lub nowszy):

Microsoft.AspNetCore.All
Microsoft.Extensions.Http

Dodaj to do Startup.cs:

services.AddHttpClient();

Wstrzyknąć i użyć:

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

Przejrzyj serię postów na blogu Steve'a, aby uzyskać o wiele więcej funkcji.


4

W moim przypadku tworzyłem HttpClient w metodzie, która faktycznie wykonała wywołanie usługi. Coś jak:

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

W roli pracownika Azure, po wielokrotnym wywoływaniu tej metody (bez pozbywania się HttpClient), ostatecznie nie powiedzie się SocketException(próba połączenia nie powiodła się).

Zrobiłem HttpClient zmienną instancji (usuwając ją na poziomie klasy) i problem zniknął. Powiedziałbym więc, tak, pozbyć się HttpClient, zakładając, że jest bezpieczny (nie masz zaległych połączeń asynchronicznych), aby to zrobić.


Dzięki za opinie. Jest to dość złożony problem. Polecam przeczytanie artykułów powiązanych w odpowiedzi DPeden. Krótko mówiąc, instancja HttpClient powinna być ponownie wykorzystywana przez cały cykl życia aplikacji. Jeśli wielokrotnie tworzysz nowe wystąpienia, może być konieczne ich usunięcie.
Fernando Correia,

6
„Instancja HttpClient powinna być ponownie wykorzystywana przez cały cykl życia aplikacji”, to nie jest dobry pomysł w przypadku wielu aplikacji. Myślę, że aplikacje internetowe używają HttpClient. HttpClient utrzymuje stan (na przykład nagłówki żądań, których będzie używał), więc jeden wątek żądania WWW może łatwo podeptać to, co robi inny. W dużych aplikacjach internetowych widziałem również HttpClient jako problem poważnych problemów z połączeniem. W razie wątpliwości mówię Pozbądź się.
bytedev

@nashwan nie możesz wyczyścić nagłówków i dodawać nowych przed każdym żądaniem?
Mandeep Janjua

Microsoft zaleca również ponowne użycie instancji HttpClient - docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/…
Mandeep Janjua

@ MandeepJanjua ten przykład wydaje się być klientem jako aplikacja konsoli. Miałem na myśli aplikację internetową będącą klientem.
bytedev

3

W typowym użyciu (odpowiedzi <2 GB) nie trzeba usuwać wiadomości HttpResponseMessages.

Zwracane typy metod HttpClient powinny zostać usunięte, jeśli ich zawartość strumienia nie jest w pełni odczytana. W przeciwnym razie CLR nie będzie wiedział, że te strumienie mogą zostać zamknięte, dopóki nie zostaną usunięte.

  • Jeśli odczytujesz dane do bajtu [] (np. GetByteArrayAsync) lub łańcucha, wszystkie dane są odczytywane, więc nie ma potrzeby ich usuwania.
  • Inne przeciążenia domyślnie odczytują strumień do 2 GB (HttpCompletionOption to ResponseContentRead, HttpClient.MaxResponseContentBufferSize domyślnie wynosi 2 GB)

Jeśli ustawisz HttpCompletionOption na ResponseHeadersRead lub odpowiedź jest większa niż 2 GB, powinieneś posprzątać. Można to zrobić, wywołując Dispose w HttpResponseMessage lub wywołując Dispose / Close w strumieniu uzyskanym z HttpResonseMessage Content lub całkowicie czytając treść.

To, czy zadzwonisz Dispose na HttpClient, zależy od tego, czy chcesz anulować oczekujące żądania, czy nie.


2

Jeśli chcesz pozbyć się HttpClient, możesz to zrobić, jeśli skonfigurujesz go jako pulę zasobów. Na koniec aplikacji pozbywasz 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 (nowy Uri („podstawowy adres URL”)).

  • HttpClient, jako interfejs, nie może wywoływać Dispose ().
  • Dispose () zostanie wywołany z opóźnieniem przez Garbage Collector. Lub gdy program czyści obiekt za pomocą destruktora.
  • Wykorzystuje słabe referencje + logikę opóźnionego czyszczenia, dzięki czemu pozostaje w użyciu, dopóki jest często używany.
  • Przydziela tylko nowego HttpClient dla każdego przekazanego mu podstawowego adresu URL. Powody wyjaśnione poniżej przez Ohada Schneidera. Złe zachowanie podczas zmiany podstawowego adresu URL.
  • HttpClientHandle pozwala na wyśmiewanie się w testach

Doskonały. Widzę, że wywołujesz Disposemetodę zarejestrowaną w GC. To powinno być ocenione wyżej na górze.
Jeson Martajaya

Zauważ, że HttpClient wykonuje pule zasobów dla podstawowego adresu URL. Więc jeśli odwiedzasz tysiące różnych stron internetowych na liście, Twoja wydajność spadnie bez czyszczenia tych poszczególnych stron. To ujawnia zdolność do usuwania każdego podstawowego adresu URL. Jeśli jednak korzystasz tylko z jednej strony internetowej, zadzwoń do pozbywania się może być tylko z powodów akademickich.
TamusJRoyce

1

Zastosowanie wstrzykiwania zależności w konstruktorze ułatwia zarządzanie czasem życia urządzenia HttpClient- eliminuje konieczność zarządzania czasem życia poza kodem, który go potrzebuje, i ułatwia jego zmianę w późniejszym terminie.

Moje obecne preferencje to utworzenie oddzielnej klasy klienta HTTP, która dziedziczy HttpClientpo raz na docelową domenę punktu końcowego, a następnie uczynienie z niej singletonu przy użyciu wstrzykiwania zależności.public class ExampleHttpClient : HttpClient { ... }

Następnie biorę zależność od konstruktora od niestandardowego klienta HTTP w klasach usług, w których potrzebuję dostępu do tego interfejsu API. Rozwiązuje to problem związany z czasem życia i ma zalety, jeśli chodzi o pule połączeń.

Możesz zobaczyć działający przykład w pokrewnej odpowiedzi na https://stackoverflow.com/a/50238944/3140853



-2

Myślę, że należy użyć wzorca singletonu, 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. Aby uzyskać więcej informacji na temat wzoru singletonu, sprawdź 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
Coś zwrócić uwagę, gdy robi to (i inne podobne programy): „ Wszelkie członków instancji nie są gwarancją pewności wątek.
tne

2
To, czy ta odpowiedź jest poprawna, czy nie, powinno całkowicie zależeć od tego, z której aplikacji chcesz korzystać HttpClient. Jeśli masz aplikację internetową i utworzysz singleton HttpClient, który wszystkie żądania WWW będą udostępniać, potencjalnie otrzymasz wiele wyjątków połączenia (w zależności od popularności Twojej witryny! :-)). (Zobacz odpowiedź Davida
Faivre'a
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.