webentwicklung-frage-antwort-db.com.de

Müssen HttpClient und HttpClientHandler entsorgt werden?

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

In der Dokumentation der Anweisung using 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 weder explizit noch implizit Dispose() auf. Zum Beispiel:

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

Nachdem Sie Ihre Beispiele überprüft haben, habe ich festgestellt, dass Sie die Aktion "dispose" für die Instanz "HttpClient" nicht ausgeführt haben. Ich habe alle Instanzen von HttpClient mit using-Anweisung 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 korrekt, obwohl Sie vorsichtig mit "using" und "async" sein müssen, da sie sich in .Net 4 nicht wirklich vermischen. In .Net 4.5 können Sie "await" innerhalb einer "using" -Anweisung verwenden.

Übrigens können Sie denselben HTTP-Client so oft [wie] Sie möchten wiederverwenden, sodass Sie ihn in der Regel nicht ständig erstellen/entsorgen.

Der zweite Absatz ist überflüssig für diese Frage, bei der es nicht darum geht, 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 angegeben.)

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 irgendwelche negativen Konsequenzen für die Nichtentsorgung gibt, wie etwa das Risiko von Ressourcenlecks oder Datenkorruption.

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

  3. Wenn es notwendig (oder empfohlen) ist dieser Code oben erwähnt, um es sicher zu implementieren (für .NET Framework 4.5)?

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

  5. Sind die Beispiele von Microsoft irreführend oder unsicher, wenn dies erforderlich ist oder empfohlen wird?

299

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

Viele Leute, die eng in die Funktionsweise involviert sind, haben dies angegeben.

Siehe Darrel Millers Blogpost und einen verwandten SO Post: HttpClient-Crawling führt zu Speicherverlust als Referenz.

Ich empfehle Ihnen auch dringend, das Kapitel HttpClient aus Entwickeln von entwicklungsfähigen Web-APIs mit ASP.NET für den Kontext zu lesen läuft unter der Haube, insbesondere im Abschnitt "Lebenszyklus", der hier zitiert wird:

Obwohl HttpClient die IDisposable-Schnittstelle indirekt implementiert, besteht die Standardverwendung von HttpClient nicht darin, sie nach jeder Anforderung zu entsorgen. Das HttpClient-Objekt ist so lange gültig, wie Ihre Anwendung HTTP-Anforderungen stellen muss. Das Vorhandensein eines Objekts über mehrere Anforderungen hinweg ermöglicht das Festlegen von DefaultRequestHeaders und verhindert, dass Sie bei jeder Anforderung Dinge wie CredentialCache und CookieContainer neu angeben müssen, wie dies bei HttpWebRequest erforderlich war.

Oder öffnen Sie sogar DotPeek.

237
David Peden

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 IDisposable Objekte idealerweise entsorgt werden, wenn Sie mit ihnen fertig sind , insbesondere diejenigen, die own Named/shared) Betriebssystemressourcen . HttpClient ist keine Ausnahme, da as Darrel Miller darauf hinweist, dass Storno-Token zugewiesen werden und Anforderungs-/Antwort-Body nicht verwaltete Streams sein können.
  2. Die Best Practice für HttpClient besagt jedoch, dass Sie eine Instanz erstellen und sie so oft wie möglich wiederverwenden sollten (unter Verwendung der thread-sicheren Elemente in Szenarien mit mehreren Threads). Daher werden Sie in den meisten Szenarien niemals darüber verfügen, nur weil Sie es die ganze Zeit benötigen werden .
  3. Das Problem bei der Wiederverwendung desselben HttpClient "für immer" besteht darin, dass die zugrunde liegende HTTP-Verbindung für die ursprünglich DNS-aufgelöste IP-Adresse möglicherweise weiterhin offen ist, unabhängig von DNS-Änderungen . Dies kann ein Problem in Szenarien wie Blue/Green-Bereitstellung und DNS-basiertem Failover sein. Es gibt verschiedene Ansätze, um dieses Problem zu lösen. Der zuverlässigste besteht darin, dass der Server ein Connection:close Header nach DNS-Änderungen. Eine andere Möglichkeit besteht darin, das HttpClient auf der Clientseite entweder regelmäßig oder über einen Mechanismus, der über die DNS-Änderung informiert wird, zu recyceln. Weitere Informationen finden Sie unter https://github.com/dotnet/corefx/issues/11224 (Ich empfehle, diese sorgfältig zu lesen, bevor Sie den im verlinkten Blogbeitrag vorgeschlagenen Code blind verwenden).
39
Ohad Schneider

Nach meinem Verständnis ist der Aufruf von Dispose() nur erforderlich, wenn Ressourcen gesperrt werden sollen, 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 es nicht tun sollten im Allgemeinen Halten Sie an Ressourcen fest, die Sie nicht verwenden (Wortspiel beabsichtigt).

Das Microsoft-Beispiel ist nicht unbedingt falsch. Alle verwendeten Ressourcen werden freigegeben, wenn die Anwendung beendet wird. Und im Fall dieses Beispiels geschieht dies fast unmittelbar nachdem HttpClient verwendet wurde. In ähnlichen Fällen ist der explizite Aufruf von Dispose() etwas überflüssig.

Wenn eine Klasse jedoch IDisposable implementiert, sollten Sie im Allgemeinen Dispose() ihrer Instanzen anzeigen, sobald Sie vollständig bereit und in der Lage sind. Ich gehe davon aus, dass dies insbesondere in Fällen wie HttpClient der Fall ist, in denen nicht explizit dokumentiert ist, ob Ressourcen oder Verbindungen aufrechterhalten oder geöffnet werden. In dem Fall, dass die Verbindung [bald] wieder verwendet wird, möchten Sie auf Dipose() verzichten - in diesem Fall sind Sie nicht "vollständig" bereit.

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

17
svidgen

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 nicht dispose aufrufen, werden die http-Verbindungen von ServicePointManager.MaxServicePointIdleTime geschlossen, das von einem Zeitgeber ausgeführt wird. 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, können Sie sicher sein, dass Sie nicht dispose anrufen und den Zeitgeber für Leerlaufverbindungen aktivieren und die Verbindungen für Sie schließen. Es ist jedoch besser, wenn Sie dispose in einer using-Anweisung aufrufen, wenn Sie wissen, dass Sie mit einer HttpClient-Instanz fertig sind und die Ressourcen schneller freigeben.

8

In meinem Fall habe ich einen HTTP-Client in einer Methode erstellt, die den Dienstaufruf tatsächlich ausgeführt hat. So etwas wie:

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

In einer Azure-Worker-Rolle schlug die Ausführung dieser Methode nach wiederholtem Aufrufen (ohne den HttpClient zu entsorgen) mit SocketException fehl (Verbindungsversuch fehlgeschlagen).

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

4
David Faivre

Bei normaler Verwendung (Antworten <2 GB) ist es nicht erforderlich, die HttpResponseMessages zu entsorgen.

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

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

Wenn Sie HttpCompletionOption auf ResponseHeadersRead setzen oder die Antwort größer als 2 GB ist, sollten Sie aufräumen. 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 HTTP-Client aufrufen, hängt davon ab, ob Sie ausstehende Anforderungen stornieren möchten oder nicht.

3
Tom Deseyn

Wenn Sie HttpClient löschen möchten, können Sie es als Ressourcenpool einrichten. Und am Ende Ihrer Bewerbung entsorgen Sie 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 durch seinen Destruktor aufräumt.
  • Verwendet schwache Referenzen und eine verzögerte Bereinigungslogik, sodass sie so lange verwendet wird, wie sie häufig wiederverwendet wird.
  • Es wird nur ein neuer HTTP-Client für jede übergebene Basis-URL zugewiesen. Gründe, die von Ohad Schneider erklärt wurden, beantworten Sie unten. Schlechtes Verhalten beim Ändern der Basis-URL.
  • HttpClientHandle ermöglicht das Verspotten von Tests
2
TamusJRoyce

Durch die Verwendung von Dependency Injection in Ihrem Konstruktor wird die Verwaltung der Lebensdauer Ihres HttpClient vereinfacht, da der Lebensdauermanagemant nicht mehr in dem Code enthalten ist, der ihn benötigt, und zu einem späteren Zeitpunkt einfach geändert werden kann.

Derzeit ist es meine Vorliebe, eine separate http-Client-Klasse zu erstellen , die einmal pro Zielendpunktdomäne von HttpClient erbt und sie dann als Singleton zu verwenden Abhängigkeitsspritze. public class ExampleHttpClient : HttpClient { ... }

Dann gehe ich eine Konstruktorabhängigkeit vom benutzerdefinierten http-Client in den Serviceklassen ein, für die ich Zugriff auf diese API benötige. Dies löst das Lebenszeitproblem und hat Vorteile beim Verbindungspooling.

Ein funktionierendes Beispiel finden Sie in der zugehörigen Antwort unter https://stackoverflow.com/a/50238944/314085

1
alastairtree

Da es den Anschein hat, dass es hier noch niemand erwähnt hat, verwenden Sie HttpClientFactory , um HttpClient und HttpClientHandler in .Net Core 2.1 am besten zu verwalten.

Es löst die meisten der oben genannten Probleme und Probleme auf eine saubere und benutzerfreundliche Art und Weise. Aus Steve Gordons großartiger 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);
    }
}

Weitere Funktionen finden Sie in der Postserie in Steves Blog.

1
pcdev

Sie müssen Dispose nicht aufrufen, da HttpClient die HttpMessageInvoker-Klasse und die HttpMessageInvoker-Implementierungs-IDisposal-Schnittstelle übernimmt und HttpClientHandler die HttpMessageHandler-Klasse und die HttpMessageHandler-Implementierungs-IDisposal-Schnittstelle übernimmt

0