Müssen HttpClient und HttpClientHandler zwischen Anforderungen angeordnet werden?

333

System.Net.Http.HttpClient und System.Net.Http.HttpClientHandler in .NET Framework 4.5 implementieren IDisposable (über System.Net.Http.HttpMessageInvoker ).

In der usingAnweisungsdokumentation heißt es:

Wenn Sie ein IDisposable-Objekt verwenden, sollten Sie es in der Regel in einer using-Anweisung deklarieren und instanziieren.

Diese Antwort verwendet dieses Muster:

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

Die sichtbarsten Beispiele von Microsoft rufen jedoch Dispose()weder explizit noch implizit auf. Zum Beispiel:

In den Kommentaren der Ankündigung fragte jemand den Microsoft-Mitarbeiter:

Nachdem ich Ihre Beispiele überprüft hatte, stellte ich fest, dass Sie die Dispose-Aktion für die HttpClient-Instanz nicht ausgeführt haben. Ich habe alle Instanzen von HttpClient mit using-Anweisungen in meiner App verwendet und dachte, dass dies der richtige Weg ist, da HttpClient die IDisposable-Schnittstelle implementiert. Bin ich auf dem richtigen Weg?

Seine Antwort war:

Im Allgemeinen ist das richtig, obwohl Sie mit "using" und async vorsichtig sein müssen, da sie sich nicht wirklich in .Net 4 mischen. In .Net 4.5 können Sie "await" in einer "using" -Anweisung verwenden.

Übrigens können Sie denselben HttpClient so oft wiederverwenden, wie Sie möchten, sodass Sie ihn normalerweise nicht immer erstellen / entsorgen.

Der zweite Absatz ist für diese Frage überflüssig. Dabei geht es nicht darum, wie oft Sie eine HttpClient-Instanz verwenden können, sondern darum, ob sie entsorgt werden muss, nachdem Sie sie nicht mehr benötigen.

(Update: Tatsächlich ist dieser zweite Absatz der Schlüssel zur Antwort, wie unten von @DPeden bereitgestellt.)

Meine Fragen sind also:

  1. Ist es angesichts der aktuellen Implementierung (.NET Framework 4.5) erforderlich, Dispose () für HttpClient- und HttpClientHandler-Instanzen aufzurufen? Klarstellung: Mit "notwendig" meine ich, wenn es negative Konsequenzen für die Nichtentsorgung gibt, wie z. B. Ressourcenverlust oder Datenkorruptionsrisiken.

  2. Wenn es nicht notwendig ist, wäre es trotzdem eine "gute Praxis", da sie IDisposable implementieren?

  3. Wenn es notwendig (oder empfohlen) ist, implementiert dieser oben erwähnte Code ihn sicher (für .NET Framework 4.5)?

  4. Wenn für diese Klassen kein Aufruf von Dispose () erforderlich ist, warum wurden sie dann als IDisposable implementiert?

  5. Sind die Microsoft-Beispiele irreführend oder unsicher, wenn sie erforderlich sind oder wenn dies empfohlen wird?

Fernando Correia
quelle
2
@Damien_The_Unbeliever, vielen Dank für Ihr Feedback. Haben Sie Vorschläge, wie ich die Frage klären könnte? Ich möchte wissen, ob dies zu Problemen führen kann, die im Allgemeinen mit der Nichtentsorgung von Ressourcen verbunden sind, wie z. B. Ressourcenverlust und Datenkorruption.
Fernando Correia
9
@ Damien_The_Unbeliever: Nicht wahr. Insbesondere Stream-Writer müssen über ein korrektes Verhalten verfügen.
Stephen Cleary
1
@StephenCleary - an welche Aspekte denkst du? Natürlich können Sie Flushnach jedem Schreibvorgang einen aufrufen , und abgesehen von der Unannehmlichkeit, die zugrunde liegenden Ressourcen länger als nötig zu halten, was wird nicht passieren, das für "korrektes Verhalten" erforderlich ist?
Damien_The_Unbeliever
1
Dies ist eindeutig falsch: "Wenn Sie ein IDisposable-Objekt verwenden, sollten Sie es in der Regel in einer using-Anweisung deklarieren und instanziieren." Ich würde die Dokumentation über die Klasse, die IDisposable implementiert, immer lesen, bevor ich mich entscheide, ob ich eine Verwendung dafür verwenden soll. Als Autor von Bibliotheken, in denen ich IDisposable implementiere, weil unveränderte Ressourcen freigegeben werden müssen, wäre ich entsetzt, wenn Verbraucher jedes Mal eine Instanz entsorgen würden, anstatt eine vorhandene Instanz erneut zu verwenden. Das heißt nicht, dass Sie die Instanz nicht irgendwann entsorgen müssen.
markmnl
1
Ich habe eine PR bei Microsoft eingereicht, um deren Dokumente zu aktualisieren: github.com/dotnet/docs/pull/2470
markmnl

Antworten:

258

Der allgemeine Konsens ist, dass Sie HttpClient nicht entsorgen müssen (sollten).

Viele Leute, die eng in die Funktionsweise involviert sind, haben dies erklärt.

Siehe Darrel Millers Blog-Beitrag und einen verwandten SO-Beitrag: Das Crawlen von HttpClient führt zu einem Speicherverlust als Referenz.

Ich würde auch dringend empfehlen, dass Sie das Kapitel HttpClient unter Entwerfen von entwicklungsfähigen Web-APIs mit ASP.NET lesen, um zu erfahren, was unter der Haube vor sich geht, insbesondere den hier zitierten Abschnitt "Lebenszyklus":

Obwohl HttpClient die IDisposable-Schnittstelle indirekt implementiert, besteht die Standardverwendung von HttpClient nicht darin, sie nach jeder Anforderung zu entsorgen. Das HttpClient-Objekt soll so lange leben, wie Ihre Anwendung HTTP-Anforderungen stellen muss. Wenn ein Objekt über mehrere Anforderungen hinweg vorhanden ist, können Sie DefaultRequestHeaders festlegen und müssen bei jeder Anforderung nicht wie bei HttpWebRequest erforderliche Elemente wie CredentialCache und CookieContainer neu angeben.

Oder öffnen Sie sogar DotPeek.

David Peden
quelle
63
Wäre es zur Klärung Ihrer Antwort richtig zu sagen, dass "Sie HttpClient nicht entsorgen müssen, wenn Sie an der Instanz festhalten, es später wiederzuverwenden"? Wenn beispielsweise eine Methode wiederholt aufgerufen wird und eine neue HttpClient-Instanz erstellt (obwohl dies in den meisten Fällen nicht das empfohlene Muster ist), ist es dennoch richtig zu sagen, dass diese Methode die Instanz nicht entsorgen sollte (die nicht wiederverwendet wird)? Dies könnte zu Tausenden von nicht verfügbaren Fällen führen. Mit anderen Worten, dass Sie versuchen sollten, die Instanzen wiederzuverwenden, aber wenn Sie sie nicht wiederverwenden, sollten Sie sie besser entsorgen (um die Verbindungen freizugeben)?
Fernando Correia
8
Ich denke, die verständlicherweise frustrierende, aber richtige Antwort ist, dass es darauf ankommt. Wenn ich an allgemeine Ratschläge gebunden sein müsste, die in den meisten Fällen (ich sage nie alle) funktionieren, würde ich vorschlagen, dass Sie einen IoC-Container verwenden und eine Instanz von HttpClient als Singleton registrieren. Die Lebensdauer der Instanz würde dann auf die Lebensdauer des Containers begrenzt. Dies kann auf Anwendungsebene oder möglicherweise pro Anforderung in einer Webanwendung erfolgen.
David Peden
25
@ FernandoCorreia Ja. Wenn Sie aus irgendeinem Grund wiederholt HttpClient-Instanzen erstellen und zerstören, sollten Sie diese entsorgen. Ich schlage nicht vor, die IDisposable-Oberfläche zu ignorieren, sondern nur zu versuchen, die Benutzer zur Wiederverwendung von Instanzen zu ermutigen.
Darrel Miller
20
Um dieser Antwort noch mehr Glaubwürdigkeit zu verleihen, habe ich heute mit dem HttpClient-Team gesprochen und sie haben bestätigt, dass HttpClient nicht für die Verwendung pro Anfrage ausgelegt ist. Eine Instanz von HttpClient sollte am Leben bleiben, während eine Clientanwendung weiterhin mit einem bestimmten Host interagiert.
Darrel Miller
19
@DavidPeden Das Registrieren von HttpClient als Singleton klingt für mich gefährlich, weil es veränderlich ist. Würden zum Beispiel nicht alle, die der TimeoutImmobilie zugewiesen sind, aufeinander stampfen?
Jon-Eric
47

Die aktuellen Antworten sind etwas verwirrend und irreführend, und es fehlen einige wichtige DNS-Implikationen. Ich werde versuchen zusammenzufassen, wo die Dinge klar stehen.

  1. Im Allgemeinen sollten die meisten IDisposableObjekte idealerweise entsorgt werden, wenn Sie damit fertig sind , insbesondere diejenigen, die Ressourcen für benannte / gemeinsam genutzte Betriebssysteme besitzen . DiesHttpClient ist keine Ausnahme, da, wie Darrel Miller betont, Stornierungs-Token zugewiesen werden und Anforderungs- / Antwort-Körper nicht verwaltete Streams sein können.
  2. Die bewährte Methode für HttpClient besagt jedoch, dass Sie eine Instanz erstellen und so oft wie möglich wiederverwenden sollten (unter Verwendung der thread-sicheren Elemente in Multithread-Szenarien). Daher werden Sie es in den meisten Szenarien niemals entsorgen, nur weil Sie es ständig benötigen .
  3. Das Problem bei der "ewigen" Wiederverwendung desselben HttpClient besteht darin, dass die zugrunde liegende HTTP-Verbindung unabhängig von DNS-Änderungen möglicherweise für die ursprünglich DNS-aufgelöste IP offen bleibt . Dies kann ein Problem in Szenarien wie der blau / grünen Bereitstellung und dem DNS-basierten Failover sein . Es gibt verschiedene Ansätze, um dieses Problem zu beheben. Der zuverlässigste ist, dass der Server Connection:closenach DNS-Änderungen einen Header sendet . Eine andere Möglichkeit besteht darin, das HttpClientauf der Clientseite entweder regelmäßig oder über einen Mechanismus zu recyceln, der Informationen über die DNS-Änderung erhält. Weitere Informationen finden Sie unter https://github.com/dotnet/corefx/issues/11224 (ich empfehle, diese sorgfältig zu lesen, bevor Sie den im verlinkten Blog-Beitrag vorgeschlagenen Code blind verwenden).
Ohad Schneider
quelle
Ich entsorge es die ganze Zeit, da ich den Proxy einer Instanz nicht wechseln kann;)
ed22
Wenn Sie den HttpClient aus irgendeinem Grund entsorgen müssen, sollten Sie eine statische Instanz des HttpMessageHandler beibehalten, da diese tatsächlich die Ursache für die Probleme ist, die der Entsorgung des HttpClient zugeschrieben werden. HttpClient verfügt über eine Konstruktorüberladung, mit der Sie festlegen können, dass der bereitgestellte Handler nicht entsorgt werden soll. In diesem Fall können Sie den HttpMessageHandler mit anderen HttpClient-Instanzen wiederverwenden.
Tom Lint
Sie sollten an Ihrem HttpClient festhalten, aber Sie können so etwas wie System.Net.ServicePointManager.DnsRefreshTimeout = 3000 verwenden. Dies ist nützlich, z. B. wenn Sie sich auf einem mobilen Gerät befinden, das jederzeit zwischen WLAN und 4G wechseln kann.
Johan Franzén
18

Nach meinem Verständnis ist ein Aufruf Dispose()nur erforderlich, wenn Ressourcen gesperrt werden, die Sie später benötigen (z. B. eine bestimmte Verbindung). Es wird immer empfohlen , Ressourcen freizugeben, die Sie nicht mehr verwenden, auch wenn Sie sie nicht mehr benötigen, einfach weil Sie im Allgemeinen nicht an Ressourcen festhalten sollten, die Sie nicht verwenden (Wortspiel beabsichtigt).

Das Microsoft-Beispiel ist nicht unbedingt falsch. Alle verwendeten Ressourcen werden beim Beenden der Anwendung freigegeben. Und im Fall dieses Beispiels geschieht dies fast unmittelbar nach der HttpClientVerwendung des. In ähnlichen Fällen ist ein explizites Aufrufen Dispose()etwas überflüssig.

Wenn eine Klasse implementiert wird IDisposable, ist das Verständnis im Allgemeinen, dass Sie Dispose()von ihren Instanzen sprechen sollten, sobald Sie vollständig bereit und in der Lage sind. Ich würde davon ausgehen, dass dies insbesondere in Fällen zutrifft, in HttpClientdenen nicht explizit dokumentiert ist, ob Ressourcen oder Verbindungen gehalten / geöffnet werden. In dem Fall, in dem die Verbindung [bald] wieder verwendet wird, möchten Sie darauf verzichten Dipose()- in diesem Fall sind Sie nicht "vollständig bereit".

Siehe auch: IDisposable.Dispose-Methode und Zeitpunkt des Aufrufs von Dispose

svidgen
quelle
7
Es ist, als würde jemand eine Banane zu Ihnen nach Hause bringen, sie essen und sie mit der Schale stehen lassen. Was sollen sie mit der Schale machen? ... Wenn sie damit auf dem Weg zur Tür sind, lassen Sie sie gehen. Wenn sie hier bleiben, lassen Sie sie es in den Müll werfen, damit es nicht stinkt.
Svidgen
Um diese Antwort zu verdeutlichen, sagen Sie: "Es ist nicht erforderlich, zu entsorgen, wenn das Programm direkt nach der Verwendung beendet wird." Und dass Sie entsorgen sollten, wenn erwartet wird, dass das Programm für einige Zeit fortgesetzt wird und andere Dinge tut?
Fernando Correia
@FernandoCorreia Ja, wenn ich etwas nicht vergesse, denke ich, dass dies ein sicheres Prinzip ist. Denken Sie aber in jedem Fall darüber nach. Wenn Sie beispielsweise mit einer Verbindung arbeiten, möchten Sie diese nicht Dispose()vorzeitig beenden und müssen einige Sekunden später erneut eine Verbindung herstellen, wenn die vorhandene Verbindung wiederverwendbar ist. Ebenso möchten Sie nicht unnötig Dispose()Bilder oder andere Strukturen erstellen, die Sie möglicherweise in ein oder zwei Minuten neu erstellen müssen.
Svidgen
Ich verstehe. Halten sie in dem speziellen Fall von HttpClient und HttpClientHandler, um den es in dieser Frage geht, eine Ressource offen, z. B. eine HTTP-Verbindung? Wenn das passiert, muss ich möglicherweise mein Verwendungsmuster überdenken.
Fernando Correia
1
@DPeden Deine Antwort steht überhaupt nicht im Widerspruch zu meiner. Beachten Sie, ich sagte, Sie sollten () seiner Instanzen entsorgen, sobald Sie vollständig bereit und in der Lage sind . Wenn Sie die Instanz erneut verwenden möchten, sind Sie noch nicht bereit .
Svidgen
9

Dispose () ruft den folgenden Code auf, der die von der HttpClient-Instanz geöffneten Verbindungen schließt. Der Code wurde durch Dekompilieren mit dotPeek erstellt.

HttpClientHandler.cs - Entsorgen

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

Wenn Sie dispose nicht aufrufen, schließt ServicePointManager.MaxServicePointIdleTime, das von einem Timer ausgeführt wird, die http-Verbindungen. Der Standardwert beträgt 100 Sekunden.

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

Wenn Sie die Leerlaufzeit nicht auf unendlich eingestellt haben, ist es sicher, dispose nicht aufzurufen und den Leerlaufverbindungs-Timer einzuschalten und die Verbindungen für Sie zu schließen, obwohl es für Sie besser wäre, dispose in einer using-Anweisung aufzurufen, wenn Sie wissen, dass Sie mit einer HttpClient-Instanz fertig sind und die Ressourcen schneller freigeben.

Timothy Gonzalez
quelle
1
Sie können den Code auch auf Github sehen. github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/…
TamusJRoyce
8

Kurze Antwort: Nein, die Aussage in der aktuell akzeptierten Antwort ist NICHT korrekt : "Der allgemeine Konsens ist, dass Sie HttpClient nicht entsorgen müssen (sollten)."

Lange Antwort : BEIDE der folgenden Aussagen sind wahr und gleichzeitig erreichbar:

  1. "HttpClient soll einmal instanziiert und während der gesamten Lebensdauer einer Anwendung wiederverwendet werden", zitiert aus der offiziellen Dokumentation .
  2. Es IDisposablewird angenommen / empfohlen, ein Objekt zu entsorgen.

Und sie stehen nicht unbedingt in Konflikt miteinander. Es geht nur darum, wie Sie Ihren Code organisieren, um ein HttpClientUND wiederzuverwenden und es dennoch ordnungsgemäß zu entsorgen.

Eine noch längere Antwort aus meiner anderen Antwort :

Es ist kein Zufall, dass Leute in einigen Blog-Posts beschuldigen HttpClient, IDisposabledass sie aufgrund der Benutzeroberfläche dazu neigen, das using (var client = new HttpClient()) {...}Muster zu verwenden , und dann zu einem erschöpften Socket-Handler-Problem führen.

Ich glaube, das kommt auf eine unausgesprochene (falsche?) Vorstellung an: "Es wird erwartet, dass ein IDisposable-Objekt nur von kurzer Dauer ist" .

JEDOCH, obwohl es sicherlich wie eine kurzlebige Sache aussieht, wenn wir Code in diesem Stil schreiben:

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

In der offiziellen Dokumentation zu IDisposable wird niemals erwähnt, dass IDisposableObjekte nur von kurzer Dauer sein müssen. Per Definition ist IDisposable lediglich ein Mechanismus, mit dem Sie nicht verwaltete Ressourcen freigeben können. Nichts mehr. In diesem Sinne wird von Ihnen ERWARTET, dass Sie die Entsorgung irgendwann auslösen, dies erfordert jedoch nicht von kurzer Dauer.

Es ist daher Ihre Aufgabe, anhand der Lebenszyklusanforderungen Ihres realen Objekts richtig zu entscheiden, wann die Entsorgung ausgelöst werden soll. Nichts hindert Sie daran, ein IDisposable auf langlebige Weise zu verwenden:

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

Mit diesem neuen Verständnis können wir jetzt, wenn wir diesen Blog-Beitrag erneut besuchen , deutlich feststellen, dass der "Fix" HttpClienteinmal initialisiert , aber niemals entsorgt wird. Deshalb können wir anhand seiner Netstat-Ausgabe erkennen, dass die Verbindung im Status ESTABLISHED bleibt, was bedeutet, dass sie vorhanden ist NICHT richtig geschlossen worden. Wenn es geschlossen wäre, wäre sein Status stattdessen in TIME_WAIT. In der Praxis ist es keine große Sache, nach dem Ende Ihres gesamten Programms nur eine Verbindung zu verlieren, und das Blog-Poster zeigt nach dem Fix immer noch einen Leistungsgewinn. Dennoch ist es konzeptionell falsch, IDisposable zu beschuldigen und es NICHT zu entsorgen.

RayLuo
quelle
Vielen Dank für diese Erklärung. Es wird deutlich Licht in den Konsens gebracht. Wann halten Sie es Ihrer Meinung nach für richtig , anzurufen HttpClient.Dispose?
Jeson Martajaya
@JesonMartajaya, entsorgen Sie es, wenn Ihre Anwendung die httpClient-Instanz nicht mehr verwenden muss. Sie mögen denken, dass ein solcher Vorschlag vage klingt, aber tatsächlich kann er perfekt zum Lebenszyklus Ihrer HttpClient clientVariablen passen, was eine Programming-101-Sache ist, die Sie vermutlich sowieso schon tun. Möglicherweise können Sie auch noch verwenden using (...) {...}. Siehe zum Beispiel das Hello World-Beispiel in meiner Antwort.
RayLuo
7

Da es scheint nicht , dass jemand es noch hier erwähnt hat, auf die neue beste Art und Weise zu verwalten und Httpclient HttpClientHandler in .Net - Core 2.1 ist mit HttpClientFactory .

Es löst die meisten der oben genannten Probleme und Fallstricke auf saubere und benutzerfreundliche Weise. Aus Steve Gordons großartigem Blog-Beitrag :

Fügen Sie Ihrem .Net Core-Projekt (2.1.1 oder höher) die folgenden Pakete hinzu:

Microsoft.AspNetCore.All
Microsoft.Extensions.Http

Fügen Sie dies zu Startup.cs hinzu:

services.AddHttpClient();

Injizieren und verwenden:

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

Entdecken Sie die Reihe von Beiträgen in Steves Blog, um weitere Funktionen zu erhalten.

pcdev
quelle
4

In meinem Fall habe ich einen HttpClient in einer Methode erstellt, die den Dienstaufruf tatsächlich ausgeführt hat. Etwas wie:

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

In einer Azure-Worker-Rolle würde diese Methode nach wiederholtem Aufrufen (ohne den HttpClient zu entsorgen) möglicherweise fehlschlagen SocketException (Verbindungsversuch fehlgeschlagen).

Ich habe den HttpClient zu einer Instanzvariablen gemacht (auf Klassenebene verfügbar gemacht), und das Problem ist behoben. Also würde ich sagen, ja, entsorgen Sie den HttpClient, vorausgesetzt, er ist sicher (Sie haben keine ausstehenden asynchronen Aufrufe), um dies zu tun.

David Faivre
quelle
Danke für die Rückmeldung. Dies ist ein etwas komplexes Thema. Ich empfehle, die Artikel zu lesen, die in der Antwort von DPeden verlinkt sind. Kurz gesagt, die HttpClient-Instanz sollte während des gesamten Anwendungslebenszyklus wiederverwendet werden. Wenn Sie wiederholt neue Instanzen erstellen, müssen Sie diese möglicherweise entsorgen.
Fernando Correia
6
"Die HttpClient-Instanz sollte während des gesamten Anwendungslebenszyklus wiederverwendet werden." Dies ist bei vielen Anwendungen keine gute Idee. Ich denke an Webanwendungen, die HttpClient verwenden. HttpClient hält den Status (z. B. die von ihm verwendeten Anforderungsheader), sodass ein Webanforderungsthread leicht mit Füßen treten kann, was ein anderer tut. In großen Webanwendungen habe ich HttpClient auch als das Problem für größere Verbindungsprobleme gesehen. Im Zweifelsfall sage ich Entsorgen.
Bytedev
@nashwan Sie können nicht vor jeder Anfrage Header löschen und neue hinzufügen?
Mandeep Janjua
Microsoft empfiehlt außerdem, HttpClient-Instanzen wiederzuverwenden - docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/…
Mandeep Janjua
@MandeepJanjua Dieses Beispiel scheint ein Client als Konsolenanwendung zu sein. Ich bezog mich auf eine Webanwendung als Client.
Bytedev
3

In der typischen Verwendung (Antworten <2 GB) ist es nicht erforderlich, die HttpResponseMessages zu entsorgen.

Die Rückgabetypen der HttpClient-Methoden sollten entsorgt werden, wenn ihr Stream-Inhalt nicht vollständig gelesen wird. Andernfalls kann die CLR nicht wissen, dass diese Streams geschlossen werden können, bis sie durch Müll gesammelt wurden.

  • Wenn Sie die Daten in ein Byte [] (z. B. GetByteArrayAsync) oder eine Zeichenfolge lesen, werden alle Daten gelesen, sodass keine Entsorgung erforderlich ist.
  • Bei den anderen Überladungen wird der Stream standardmäßig bis zu 2 GB gelesen (HttpCompletionOption ist ResponseContentRead, HttpClient.MaxResponseContentBufferSize ist standardmäßig 2 GB).

Wenn Sie die HttpCompletionOption auf ResponseHeadersRead setzen oder die Antwort größer als 2 GB ist, sollten Sie bereinigen. Dies kann durch Aufrufen von Dispose in der HttpResponseMessage oder durch Aufrufen von Dispose / Close in dem aus dem HttpResonseMessage-Inhalt erhaltenen Stream oder durch vollständiges Lesen des Inhalts erfolgen.

Ob Sie Dispose auf dem HttpClient aufrufen, hängt davon ab, ob Sie ausstehende Anforderungen abbrechen möchten oder nicht.

Tom Deseyn
quelle
2

Wenn Sie HttpClient entsorgen möchten, können Sie dies tun, wenn Sie es als Ressourcenpool einrichten. Und am Ende Ihrer Anwendung verfügen Sie über Ihren Ressourcenpool.

Code:

// 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 (neue Uri ("Basis-URL")).

  • HttpClient kann als Schnittstelle Dispose () nicht aufrufen.
  • Dispose () wird vom Garbage Collector verzögert aufgerufen. Oder wenn das Programm das Objekt über seinen Destruktor bereinigt.
  • Verwendet schwache Referenzen + verzögerte Bereinigungslogik, sodass sie so lange verwendet wird, wie sie häufig wiederverwendet wird.
  • Es wird nur ein neuer HttpClient für jede an ihn übergebene Basis-URL zugewiesen. Gründe, die Ohad Schneider unten erklärt hat. Schlechtes Verhalten beim Ändern der Basis-URL.
  • HttpClientHandle ermöglicht das Verspotten in Tests
TamusJRoyce
quelle
Perfekt. Ich sehe, dass Sie eine DisposeMethode aufrufen , die Sie bei GC registrieren. Dies sollte oben höher bewertet werden.
Jeson Martajaya
Beachten Sie, dass HttpClient Ressourcenpools pro Basis-URL ausführt. Wenn Sie also Tausende verschiedener Websites in einer Liste aufrufen, wird Ihre Leistung beeinträchtigt, ohne dass diese einzelnen Websites bereinigt werden. Dies macht es möglich, jede Basis-URL zu entsorgen. Wenn Sie jedoch nur eine Website verwenden, ist es möglicherweise nur aus akademischen Gründen möglich, dispose anzurufen.
TamusJRoyce
1

Die Verwendung der Abhängigkeitsinjektion in Ihrem Konstruktor HttpClienterleichtert die Verwaltung der Lebensdauer Ihres Konstruktors. Nehmen Sie das Lebensdauermanagement außerhalb des Codes, der es benötigt, und machen Sie es zu einem späteren Zeitpunkt leicht änderbar.

Meine derzeitige Präferenz ist es , eine separate http-Client-KlasseHttpClient zu erstellen, die einmal pro Zielendpunktdomäne erbt, und sie dann mithilfe der Abhängigkeitsinjektion zu einem Singleton zu machen.public class ExampleHttpClient : HttpClient { ... }

Dann nehme ich eine Konstruktorabhängigkeit vom benutzerdefinierten http-Client in den Serviceklassen, auf die ich Zugriff auf diese API benötige. Dies löst das Problem der Lebensdauer und hat Vorteile beim Verbindungspooling.

Ein Beispiel finden Sie in der entsprechenden Antwort unter https://stackoverflow.com/a/50238944/3140853

Alastairtree
quelle
-2

Ich denke, man sollte ein Singleton-Muster verwenden, um zu vermeiden, dass Instanzen des HttpClient erstellt und ständig geschlossen werden müssen. Wenn Sie .Net 4.0 verwenden, können Sie einen Beispielcode wie folgt verwenden. Weitere Informationen zum Singleton-Muster finden Sie hier .

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

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

    private HttpClientSingletonWrapper()
    {
    }
}

Verwenden Sie den folgenden Code.

var client = HttpClientSingletonWrapper.Instance;
yayadavid
quelle
3
Beachten Sie
2
Ob diese Antwort richtig ist oder nicht, sollte vollständig davon abhängen, von welcher Anwendung Sie HttpClient verwenden möchten. Wenn Sie eine Webanwendung haben und einen Singleton-HttpClient erstellen, den alle Webanfragen gemeinsam nutzen würden, werden Sie möglicherweise viele Verbindungsausnahmen erhalten (abhängig davon, wie beliebt Ihre Website ist! :-)). (Siehe David
Faivres