410 Stimmen

Müssen HttpClient und HttpClientHandler zwischen Anfragen entsorgt werden?

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

Die Dokumentation zum using Statement sagt:

Im Allgemeinen sollten Sie, wenn Sie ein IDisposable-Objekt verwenden, es in einem using-Statement 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("foo", "bar"),
        new KeyValuePair("baz", "bazinga"),
    });
    cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
    var result = client.PostAsync("/test", content).Result;
    result.EnsureSuccessStatusCode();
}

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

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

Nach Überprüfung Ihrer Beispiele habe ich gesehen, dass Sie die Dispose-Aktion auf der HttpClient-Instanz nicht ausgeführt haben. Ich habe alle Instanzen von HttpClient mit dem using-Statement in meiner App verwendet und dachte, dass dies der richtige Weg ist, da HttpClient das IDisposable-Interface implementiert. Befinde ich mich auf dem richtigen Weg?

Seine Antwort war:

Im Allgemeinen ist das richtig, obwohl Sie bei "using" und async vorsichtig sein müssen, da sie in .NET 4 nicht wirklich zusammenpassen. In .NET 4.5 können Sie "await" innerhalb eines "using"-Statements verwenden.

Übrigens können Sie den gleichen HttpClient so oft wiederverwenden, wie Sie möchten, daher müssen Sie sie in der Regel nicht ständig erstellen/entsorgen.

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

(Update: In der Tat ist dieser zweite Absatz der Schlüssel zur Antwort, wie unten von @DPeden angegeben.)

Also meine Fragen sind:

  1. Ist es angesichts der aktuellen Implementierung (.NET Framework 4.5) erforderlich, Dispose() auf HttpClient- und HttpClientHandler-Instanzen aufzurufen? Klarstellung: Mit "erforderlich" meine ich, ob es negative Konsequenzen gibt, wenn Sie keine Entsorgung durchführen, wie beispielsweise Ressourcenlecks oder Risiken für Datenkorruption.

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

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

  4. Wenn diese Klassen keine Dispose()-Aufrufe erfordern, warum wurden sie dann als IDisposable implementiert?

  5. Wenn sie erforderlich sind oder es eine empfohlene Praxis ist, sind die Microsoft-Beispiele irreführend oder unsicher?

296voto

David Peden Punkte 16390

Die allgemeine Übereinstimmung ist, dass Sie HttpClient nicht entsorgen müssen (sollten).

Viele Personen, die eng mit der Funktionsweise vertraut sind, haben dies bestätigt.

Siehe Darrel Millers Blog-Beitrag und einen verwandten SO-Beitrag: HttpClient crawling führt zu Memory-Leak als Referenz.

Ich würde auch dringend empfehlen, den HttpClient-Abschnitt aus Designing Evolvable Web APIs with ASP.NET zu lesen, um den Kontext dessen zu verstehen, was unter der Haube passiert, insbesondere in Abschnitt "Lebenszyklus" zitiert hier:

Auch wenn HttpClient die IDisposable-Schnittstelle indirekt implementiert, ist die Standardanwendung von HttpClient nicht, es nach jeder Anfrage zu entsorgen. Das HttpClient-Objekt soll so lange leben, wie Ihre Anwendung HTTP-Anfragen stellen muss. Das Vorhandensein eines Objekts für mehrere Anfragen ermöglicht es, DefaultRequestHeaders festzulegen und verhindert, dass Sie Dinge wie CredentialCache und CookieContainer bei jeder Anfrage neu angeben müssen, wie dies bei HttpWebRequest erforderlich war.

Oder öffnen Sie sogar DotPeek.

77voto

Ohad Schneider Punkte 34748

Die aktuellen Antworten sind ein wenig verwirrend und irreführend und lassen einige wichtige DNS -Implikationen vermissen. Ich werde versuchen, zusammenzufassen, wo die Dinge klar stehen.

  1. Im Allgemeinen sollten die meisten IDisposable Objekte idealerweise entsorgt werden, wenn Sie mit ihnen fertig sind, insbesondere diejenigen, die benannte/ gemeinsam genutzte OS-Ressourcen besitzen. HttpClient bildet keine Ausnahme, da es, wie Darrel Miller herausstellt, Abbruchtoken allokiert und Anforderungs-/Antwortkörper nicht verwaltete Streams sein können.
  2. Die beste Praxis für HttpClient besagt jedoch, dass Sie eine Instanz erstellen sollten und sie so oft wie möglich wiederverwenden sollten (Verwendung seiner Thread-sicheren Elemente in Mehrfaden-Szenarien). Daher werden Sie in den meisten Szenarien es nie entsorgen, einfach weil Sie es die ganze Zeit benötigen werden.
  3. Das Problem bei der Wiederverwendung des gleichen HttpClient "für immer" besteht darin, dass die zugrunde liegende HTTP-Verbindung gegenüber der ursprünglich DNS-aufgelösten IP offen bleiben kann, unabhängig von DNS-Änderungen. Dies kann in Szenarien wie blau/weiss-Bereitstellung und DNS-basierte Ausfallsicherung ein Problem darstellen. Es gibt verschiedene Ansätze, um mit diesem Problem umzugehen, wobei der zuverlässigste Ansatz darin besteht, dass der Server nach DNS-Änderungen einen Connection:close Header sendet. Eine andere Möglichkeit besteht darin, den HttpClient auf der Clientseite entweder periodisch oder über einen Mechanismus zu recyceln, der über die DNS-Änderung informiert wird. Siehe https://github.com/dotnet/corefx/issues/11224 für weitere Informationen (ich empfehle, sie sorgfältig zu lesen, bevor Sie den im verlinkten Blogbeitrag vorgeschlagenen Code blind verwenden).

38voto

pcdev Punkte 2662

Nachdem es anscheinend bisher niemand hier erwähnt hat, ist der neue beste Weg, HttpClient und HttpClientHandler in .NET Core >= 2.1 und .NET 5.0+ zu verwalten, die Verwendung von HttpClientFactory.

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

Fügen Sie die folgenden Pakete zu Ihrem .NET Core-Projekt (ab 2.1.1 oder neuer) hinzu:

Microsoft.AspNetCore.All
Microsoft.Extensions.Http

Fügen Sie dies zu Startup.cs hinzu:

services.AddHttpClient();

Inject und verwenden:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public async Task Get()
    {
        var client = _httpClientFactory.CreateClient();
        var result = await client.GetStringAsync("http://www.google.com");
        return Ok(result);
    }
}

Erkunden Sie in Steves Blog-Serie viele weitere Funktionen.

19voto

svidgen Punkte 13477

Nach meinem Verständnis ist das Aufrufen von Dispose() nur notwendig, wenn es Ressourcen sperrt, die du später benötigst (wie etwa eine bestimmte Verbindung). Es wird immer empfohlen, Ressourcen freizugeben, die du nicht mehr benötigst, auch wenn du sie nicht wieder benötigst, einfach weil du im Allgemeinen keine Ressourcen festhalten solltest, die du nicht verwendest (wortwörtlich).

Das Microsoft-Beispiel ist nicht unbedingt falsch. Alle verwendeten Ressourcen werden freigegeben, wenn die Anwendung beendet wird. Und in dem Fall des Beispiels geschieht dies fast unmittelbar nachdem das HttpClient nicht mehr benötigt wird. In solchen Fällen ist das explizite Aufrufen von Dispose() etwas überflüssig.

Aber im Allgemeinen, wenn eine Klasse IDisposable implementiert, bedeutet dies, dass du die Instanzen so schnell wie möglich freigeben solltest, wenn du vollständig bereit und fähig bist. Meiner Meinung nach gilt dies insbesondere in Fällen wie dem HttpClient, bei dem nicht explizit dokumentiert ist, ob Ressourcen oder Verbindungen festgehalten bzw. geöffnet werden. Wenn die Verbindung demnächst wieder verwendet wird, solltest du auf das Dispose() verzichten - du bist in diesem Fall nicht "vollständig bereit".

Siehe auch: IDisposable.Dispose-Methode und Wann Dispose aufgerufen werden soll

14voto

RayLuo Punkte 14410

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

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

  1. "HttpClient soll einmal instanziiert und während der Laufzeit einer Anwendung wiederverwendet werden", zitiert aus der offiziellen Dokumentation.
  2. Ein IDisposable-Objekt sollte entsorgt werden.

Und sie WIDERSPRECHEN SICH NICHT NOTWENDIGERWEISE gegenseitig. Es geht nur darum, wie Sie Ihren Code organisieren, um ein HttpClient wiederzuverwenden und trotzdem ordnungsgemäß zu entsorgen.

Ein noch ausführlichere Antwort zitiert aus meiner anderen Antwort:

Es ist kein Zufall, dass Leute in einigen Blog-Beiträgen die Schuld dafür geben, wie die IDisposable-Schnittstelle von HttpClient dazu führt, dass sie dazu neigen, das using (var client = new HttpClient()) {...}-Muster zu verwenden und dann zu einem erschöpften Socket-Handler-Problem führt.

Ich glaube, dass dies auf einer stillschweigenden (Miss-)Auffassung beruht: "Ein IDisposable-Objekt wird erwartungsgemäß kurzlebig sein".

WÄHREND es sicherlich wie eine kurzlebige Sache aussieht, wenn wir Code in diesem Stil schreiben:

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

erwähnt die offizielle Dokumentation zu IDisposable nie, dass IDisposable-Objekte kurzlebig sein müssen. Nach Definition ist IDisposable lediglich ein Mechanismus, um Ihnen das Freigeben von nicht verwalteten Ressourcen zu ermöglichen. Nicht mehr. In diesem Sinne wird erwartet, dass Sie letztendlich die Entsorgung auslösen, aber es erfordert nicht, dass Sie dies kurzlebig tun.

Es liegt also an Ihnen, wann Sie die Entsorgung ordnungsgemäß auslösen, basierend auf den Anforderungen des tatsächlichen Lebenszyklus Ihres Objekts. Es hindert Sie nichts daran, IDisposable auf langfristige Weise zu verwenden:

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");

            using (var client = new HttpClient())
            {
                for (...) { ... }  // Eine wirklich lange Schleife

                // Oder Sie könnten sogar irgendwie einen Daemon hier starten

            }

            // Halten Sie das Konsolenfenster im Debug-Modus offen.
            Console.WriteLine("Drücken Sie eine beliebige Taste, um zu beenden.");
            Console.ReadKey();
        }
    }
}

Mit diesem neuen Verständnis können wir nun den besagten Blog-Beitrag erneut betrachten. Wir erkennen deutlich, dass das "Fix" initialisiert den HttpClient einmal, entsorgt ihn aber nie. Daher können wir aus seiner Netstat-Ausgabe sehen, dass die Verbindung im ZUSTAND ESTABLISHED verbleibt, was bedeutet, dass sie NICHT ordnungsgemäß geschlossen wurde. Wenn sie geschlossen wäre, wäre ihr Zustand stattdessen TIME_WAIT. In der Praxis ist es kein großes Problem, nur eine Verbindung offen zu lassen, nachdem Ihr gesamtes Programm endet, und der Blog-Autor sieht immer noch einen Leistungsgewinn nach dem Fix; dennoch ist es konzeptionell inkorrekt, IDisposable zu beschuldigen und sich dafür zu entscheiden, es NICHT zu entsorgen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X