Vor kurzem bin ich auf diesen Blog-Beitrag von asp.net-Monstern gestoßen, der über Probleme bei der Verwendung HttpClient
auf folgende Weise spricht :
using(var client = new HttpClient())
{
}
Laut dem Blog-Post HttpClient
können die TCP-Verbindungen offen bleiben , wenn wir die nach jeder Anfrage entsorgen . Dies kann möglicherweise dazu führen System.Net.Sockets.SocketException
.
Der korrekte Weg laut Post ist, eine einzelne Instanz von zu erstellen, HttpClient
da dies dazu beiträgt, die Verschwendung von Steckdosen zu reduzieren.
Von der Post:
Wenn wir eine einzelne Instanz von HttpClient gemeinsam nutzen, können wir die Verschwendung von Sockets reduzieren, indem wir sie wiederverwenden:
namespace ConsoleApplication { public class Program { private static HttpClient Client = new HttpClient(); public static void Main(string[] args) { Console.WriteLine("Starting connections"); for(int i = 0; i<10; i++) { var result = Client.GetAsync("http://aspnetmonsters.com").Result; Console.WriteLine(result.StatusCode); } Console.WriteLine("Connections done"); Console.ReadLine(); } } }
Ich habe HttpClient
Objekte immer entsorgt , nachdem ich sie verwendet habe, da ich der Meinung war, dass dies der beste Weg ist, sie zu verwenden. Aber dieser Blog-Beitrag gibt mir das Gefühl, dass ich es die ganze Zeit falsch gemacht habe.
Sollten wir HttpClient
für alle Anfragen eine neue einzelne Instanz von erstellen ? Gibt es Fallstricke bei der Verwendung statischer Instanzen?
quelle
Close()
einen neuen ausführen oder initiierenGet()
. Wenn Sie den Client nur entsorgen, wenn Sie damit fertig sind, gibt es niemanden, der diesen schließenden Handshake handhabt, und Ihre Ports haben aus diesem Grund alle den Status TIME_WAIT.Antworten:
Es scheint wie ein überzeugender Blog-Beitrag. Bevor ich jedoch eine Entscheidung traf, führte ich zunächst dieselben Tests durch, die der Blogschreiber durchgeführt hatte, jedoch mit Ihrem eigenen Code. Ich würde auch versuchen, ein bisschen mehr über HttpClient und sein Verhalten herauszufinden.
In diesem Beitrag heißt es:
Wenn also ein HTTP-Client freigegeben wird, passiert wahrscheinlich, dass die Verbindungen wiederverwendet werden. Dies ist in Ordnung, wenn Sie keine dauerhaften Verbindungen benötigen. Der einzige Weg, um sicherzugehen, ob dies für Ihre Situation von Bedeutung ist oder nicht, besteht darin, Ihre eigenen Leistungstests durchzuführen.
Wenn Sie graben, finden Sie mehrere andere Ressourcen, die dieses Problem beheben (einschließlich eines Microsoft Best Practices-Artikels). Daher ist es wahrscheinlich eine gute Idee, sie trotzdem zu implementieren (mit einigen Vorsichtsmaßnahmen).
Verweise
Sie verwenden HttpClient Wrong und es destabilisiert Ihre Software
Singleton HttpClient? Beachten Sie dieses schwerwiegende Verhalten und beheben Sie es
Microsoft Patterns and Practices - Leistungsoptimierung: Fehlerhafte Instantiierung
Einzelne wiederverwendbare Instanz von HttpClient bei der
Codeüberprüfung Singleton HttpClient berücksichtigt keine DNS-Änderungen (CoreFX)
Allgemeine Hinweise zur Verwendung von HttpClient
quelle
Ich komme zu spät zur Party, aber hier ist meine Lernreise zu diesem kniffligen Thema.
1. Wo finden wir den offiziellen Anwalt für die Wiederverwendung von HttpClient?
Ich meine, wenn die Wiederverwendung von HttpClient beabsichtigt ist und dies wichtig ist , ist ein solcher Befürworter besser in seiner eigenen API-Dokumentation dokumentiert, als in vielen "Advanced Topics", "Performance (anti) pattern" oder anderen Blog-Posts versteckt zu sein . Ansonsten, wie soll ein neuer Lernender es wissen, bevor es zu spät ist?
Ab sofort (Mai 2018) verweist das erste Suchergebnis beim Googeln von "c # httpclient" auf diese API-Referenzseite auf MSDN , die diese Absicht überhaupt nicht erwähnt. Nun, Lektion 1 hier für Anfänger ist, klicken Sie immer auf den Link "Andere Versionen" direkt nach der Überschrift der MSDN-Hilfeseite. Dort finden Sie wahrscheinlich Links zur "aktuellen Version". In diesem HttpClient-Fall werden Sie zum neuesten Dokument mit dieser Absichtsbeschreibung weitergeleitet .
Ich vermute, dass viele Entwickler, die neu in diesem Thema waren, auch nicht die richtige Dokumentationsseite gefunden haben. Deshalb ist dieses Wissen nicht weit verbreitet, und die Leute waren überrascht, als sie es später herausfanden , möglicherweise auf schwierige Weise .
2. Die (falsche?) Vorstellung von
using
IDisposable
Dies ist ein wenig vom Thema abweichend, aber es ist immer noch erwähnenswert, dass es kein Zufall ist, zu sehen, dass die Leute in diesen oben genannten Blog-Posts beschuldigen, dass
HttpClient
dieIDisposable
Benutzeroberfläche dazu neigt, dasusing (var client = new HttpClient()) {...}
Muster zu verwenden , und dann zum Problem führen.Ich glaube, dass dies auf eine unausgesprochene (falsche?) Vorstellung zurückzuführen ist: "Es wird erwartet, dass ein IDisposable-Objekt von kurzer Dauer ist" .
JEDOCH, obwohl es sicherlich kurzlebig ist, wenn wir Code in diesem Stil schreiben:
Die offizielle Dokumentation zu IDisposable erwähnt niemals, dass
IDisposable
Objekte von kurzer Dauer sein müssen. IDisposable ist per Definition lediglich ein Mechanismus, mit dem Sie nicht verwaltete Ressourcen freigeben können. Nichts mehr. In diesem Sinne wird ERWARTET, dass Sie eventuell die Entsorgung auslösen, dies ist jedoch nicht von kurzer Dauer.Es ist daher Ihre Aufgabe, den richtigen Zeitpunkt für die Entsorgung zu bestimmen, basierend auf den Anforderungen des Lebenszyklus Ihres realen Objekts. Nichts hindert Sie daran, ein IDisposable dauerhaft zu verwenden:
Mit diesem neuen Verständnis können wir jetzt, wenn wir diesen Blog-Beitrag erneut besuchen , deutlich feststellen, dass der "Fix"
HttpClient
einmal initialisiert wird, ihn aber nie wieder freigibt. Aus diesem Grund können wir anhand seiner Netstat-Ausgabe erkennen, dass die Verbindung im Zustand ESTABLISHED verbleibt, was bedeutet, dass er besteht NICHT richtig geschlossen worden. Wenn es geschlossen wäre, wäre sein Status stattdessen 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 kann nach dem Fix noch einen Leistungsgewinn verzeichnen. Dennoch ist es konzeptionell falsch, IDisposable die Schuld zu geben und sich dafür zu entscheiden, NICHT darüber zu verfügen.3. Müssen wir HttpClient in eine statische Eigenschaft oder sogar als Singleton setzen?
Basierend auf dem Verständnis des vorherigen Abschnitts wird die Antwort hier meines Erachtens klar: "nicht unbedingt". Es hängt wirklich davon ab, wie Sie Ihren Code organisieren, solange Sie einen HTTP-Client wiederverwenden UND (im Idealfall) irgendwann entsorgen.
Komischerweise stimmt das Beispiel im Abschnitt "Bemerkungen" des aktuellen offiziellen Dokuments nicht ganz. Es definiert eine "GoodController" -Klasse, die eine statische HttpClient-Eigenschaft enthält, die nicht entsorgt wird. was nicht gehorcht, was ein anderes Beispiel im Abschnitt " Beispiele" betont: "muss entsorgen ... damit die App keine Ressourcen verliert ".
Und schließlich ist Singleton nicht ohne eigene Herausforderungen.
- Zitiert aus diesem inspirierenden Vortrag "Global State and Singletons"
PS: SqlConnection
Dies ist für die aktuelle Frage und Antwort irrelevant, aber es ist wahrscheinlich gut zu wissen. Das Verwendungsmuster von SqlConnection ist unterschiedlich. Sie müssen SqlConnection NICHT erneut verwenden , da es den Verbindungspool auf diese Weise besser handhabt .
Der Unterschied ergibt sich aus dem Implementierungsansatz. Jede HttpClient-Instanz verwendet ihren eigenen Verbindungspool (von hier aus zitiert ). aber SqlConnection selbst wird von einem zentralen Verbindungspool verwaltet werden , nach diesem .
Und Sie müssen SqlConnection weiterhin wie für HttpClient entsorgen.
quelle
Ich habe einige Tests durchgeführt, bei denen Leistungsverbesserungen mit statischer Aufladung festgestellt wurden
HttpClient
. Ich habe folgenden Code für meine Tests verwendet:Zum Prüfen:
Ich fand die Leistungsverbesserung zwischen 40% und 60% bei Verwendung der statischen Aufladung,
HttpClient
anstatt sie für eineHttpClient
Anfrage zu entsorgen . Ich habe die Details des Leistungstestergebnisses in den Blog-Beitrag hier gestellt .quelle
Um die TCP-Verbindung ordnungsgemäß zu schließen , müssen Sie eine FIN-FIN + ACK-ACK-Paketsequenz ausführen (genau wie SYN-SYN + ACK-ACK, wenn Sie eine TCP-Verbindung herstellen ). Wenn wir nur eine .Close () - Methode aufrufen (normalerweise, wenn ein HttpClient verfügbar ist) und nicht darauf warten, dass die Remote-Seite unsere Anforderung zum Schließen bestätigt (mit FIN + ACK), wird der TIME_WAIT-Status aktiviert den lokalen TCP-Port, da wir unseren Listener (HttpClient) entsorgt haben und nie die Möglichkeit hatten, den Portstatus auf einen ordnungsgemäßen geschlossenen Status zurückzusetzen, sobald der Remote-Peer uns das FIN + ACK-Paket gesendet hat.
Der richtige Weg, die TCP-Verbindung zu schließen, besteht darin, die .Close () -Methode aufzurufen und zu warten, bis das Close-Ereignis von der anderen Seite (FIN + ACK) auf unserer Seite eintrifft. Erst dann können wir unser endgültiges ACK senden und den HttpClient entsorgen.
Nur zum Hinzufügen ist es sinnvoll, TCP-Verbindungen offen zu halten, wenn Sie HTTP-Anforderungen ausführen, da der HTTP-Header "Connection: Keep-Alive" verwendet wird. Außerdem können Sie die Gegenstelle auffordern, die Verbindung für Sie zu trennen, indem Sie den HTTP-Header "Connection: Close" setzen. Auf diese Weise werden Ihre lokalen Ports immer ordnungsgemäß geschlossen, anstatt sich in einem TIME_WAIT-Status zu befinden.
quelle
Hier ist ein grundlegender API-Client, der den HttpClient und den HttpClientHandler effizient verwendet. Wenn Sie einen neuen HttpClient erstellen, um eine Anfrage zu stellen, entsteht viel Overhead. Erstellen Sie HttpClient NICHT für jede Anforderung neu. HttpClient so oft wie möglich wiederverwenden ...
Verwendungszweck:
quelle
Es gibt keine Möglichkeit, die HttpClient-Klasse zu verwenden. Der Schlüssel besteht darin, Ihre Anwendung so zu gestalten, dass sie für die Umgebung und die Einschränkungen sinnvoll ist.
HTTP ist ein großartiges Protokoll, wenn Sie öffentliche APIs verfügbar machen müssen. Es kann auch effektiv für leichte interne Dienste mit geringer Latenz verwendet werden - obwohl das Muster der RPC-Nachrichtenwarteschlange für interne Dienste häufig die bessere Wahl ist.
Es ist sehr komplex, HTTP gut zu machen.
Folgendes berücksichtigen:
Vor allem aber testen, messen und bestätigen. Wenn es sich nicht wie geplant verhält, können wir spezifische Fragen beantworten, wie Sie Ihre erwarteten Ergebnisse erzielen können.
quelle
HttpClient
implementiertIDisposable
. Es ist daher nicht unangemessen, davon auszugehen, dass es sich um ein kurzlebiges Objekt handelt, das es versteht, nach sich selbst aufzuräumen, und dasusing
jedes Mal, wenn Sie es benötigen, in eine Anweisung eingebunden werden kann. Leider funktioniert es nicht so. Der vom OP verknüpfte Blogeintrag zeigt deutlich, dass es Ressourcen gibt (insbesondere TCP-Socket-Verbindungen), die lange nach demusing
Erlöschen des Gültigkeitsbereichs der Anweisung und derHttpClient
mutmaßlichen Entsorgung des Objekts weiterleben .