ostatnio natknąłem się na ten post na blogu od potworów asp.net, który mówi o problemach z używaniem HttpClient
w następujący sposób:
using(var client = new HttpClient())
{
}
Zgodnie z postem na blogu, jeśli usuwamy HttpClient
po każdym żądaniu, może utrzymać otwarte połączenia TCP. Może to potencjalnie prowadzić do System.Net.Sockets.SocketException
.
Prawidłowym sposobem według postu jest utworzenie pojedynczej instancji, HttpClient
ponieważ pomaga to ograniczyć marnotrawstwo gniazd.
Z postu:
Jeśli udostępniamy jedno wystąpienie HttpClient, możemy zmniejszyć marnotrawstwo gniazd, ponownie je wykorzystując:
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(); } } }
Zawsze korzystałem z HttpClient
przedmiotu po jego użyciu, ponieważ uważałem, że jest to najlepszy sposób na jego użycie. Ale ten post na blogu sprawia, że czuję, że przez tak długi czas robiłem to źle.
Czy powinniśmy utworzyć nową pojedynczą instancję HttpClient
dla wszystkich żądań? Czy są jakieś pułapki związane z użyciem instancji statycznej?
źródło
Close()
lub zainicjujesz nowyGet()
. Jeśli po prostu zlikwidujesz klienta, gdy skończysz z nim, nie będzie nikogo, kto poradziłby sobie z tym zamykającym uściskiem dłoni, i dlatego wszystkie porty będą miały stan TIME_WAIT.Odpowiedzi:
Wygląda na przekonujący post na blogu. Zanim jednak podejmę decyzję, najpierw przeprowadzę te same testy, które napisał bloger, ale na własnym kodzie. Chciałbym również dowiedzieć się nieco więcej o HttpClient i jego zachowaniu.
Ten post stwierdza:
Więc prawdopodobnie dzieje się, gdy HttpClient jest współdzielony, to że połączenia są ponownie wykorzystywane, co jest w porządku, jeśli nie potrzebujesz trwałych połączeń. Jedynym sposobem, aby dowiedzieć się na pewno, czy ma to znaczenie dla Twojej sytuacji, jest uruchomienie własnych testów wydajności.
Jeśli kopiesz, znajdziesz kilka innych zasobów, które rozwiązują ten problem (w tym artykuł najlepszych praktyk firmy Microsoft), więc prawdopodobnie dobrym pomysłem jest wdrożenie go (z pewnymi środkami ostrożności).
Bibliografia
Używasz Httpclient źle i to destabilizuje twoje oprogramowanie
Singleton HttpClient? Uważaj na to poważne zachowanie i jak je naprawić
Wzorce i praktyki Microsoft - Optymalizacja wydajności: niewłaściwa instancja
Pojedyncze wystąpienie wielokrotnego użytku HttpClient w recenzji kodu
Singleton HttpClient nie przestrzega zmian DNS (CoreFX)
Ogólne porady dotyczące korzystania z HttpClient
źródło
Spóźniam się na przyjęcie, ale oto moja podróż edukacyjna na ten trudny temat.
1. Gdzie możemy znaleźć oficjalnego rzecznika ponownego użycia HttpClient?
Mam na myśli, że jeśli ponowne użycie HttpClient jest zamierzone i jest to ważne , taki adwokat jest lepiej udokumentowany we własnej dokumentacji API, zamiast ukrywać się w wielu „Zaawansowanych tematach”, „Wzorcu (anty)” lub innych postach na blogu . W przeciwnym razie skąd nowy uczeń powinien to wiedzieć, zanim będzie za późno?
Na dzień dzisiejszy (maj 2018 r.) Pierwszy wynik wyszukiwania w Google „c # httpclient” wskazuje na tę stronę referencyjną interfejsu API w witrynie MSDN , która wcale nie wspomina o tej intencji. Lekcja 1 dla początkujących polega na tym, że zawsze kliknij link „Inne wersje” tuż za nagłówkiem strony pomocy MSDN, prawdopodobnie znajdziesz tam linki do „bieżącej wersji”. W tym przypadku HttpClient przeniesie Cię do najnowszego dokumentu zawierającego opis tego zamiaru .
Podejrzewam, że wielu programistów, którzy byli nowi w tym temacie, również nie znalazło właściwej strony z dokumentacją, dlatego ta wiedza nie jest szeroko rozpowszechniona, a ludzie byli zaskoczeni, gdy odkryli ją później , być może w trudny sposób .
2. (Nie?) Koncepcja
using
IDisposable
Ten jest nieco nie na temat, ale nadal warto podkreślić, że nie jest to przypadek, aby zobaczyć ludzi w tych wspomnianych blogach obwiniając jak
HttpClient
„sIDisposable
interfejs sprawia, że mają tendencję do korzystania zusing (var client = new HttpClient()) {...}
wzorca, a następnie doprowadzić do problemu.Uważam, że sprowadza się to do niewypowiedzianej (błędnej?) Koncepcji: „oczekuje się, że obiekt IDisposable będzie krótkotrwały” .
JEDNAK, chociaż na pewno wygląda to na coś krótkotrwałego, gdy piszemy kod w tym stylu:
Oficjalna dokumentacja IDisposable nie wspomina
IDisposable
obiekty 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:
Dzięki temu nowemu zrozumieniu, teraz ponownie odwiedzamy ten post na blogu , możemy wyraźnie zauważyć, że „poprawka” inicjuje się
HttpClient
raz, 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, aby przeciekać tylko jedno połączenie otwarte po zakończeniu całego programu, a plakat na blogu nadal widzi wzrost wydajności po poprawce; ale nadal koncepcyjnie niewłaściwe jest obwinianie IDisposable i NIE wyrzucanie go.3. Czy musimy umieścić HttpClient we właściwości statycznej, czy nawet jako singleton?
Na podstawie zrozumienia poprzedniej części myślę, że odpowiedź tutaj jest jasna: „niekoniecznie”. To naprawdę zależy od tego, jak zorganizujesz swój kod, pod warunkiem, że użyjesz HttpClient ORAZ (najlepiej) ostatecznie go usuniesz.
Zabawne jest, że nawet przykład w sekcji Uwagi w obecnym oficjalnym dokumencie nie ma racji. Definiuje klasę „GoodController”, zawierającą statyczną właściwość HttpClient, która nie będzie usuwana; co nie zgadza się z tym, co podkreśla inny przykład w sekcji Przykłady : „trzeba zadzwonić do dysponowania ... aby aplikacja nie przeciekała zasobów”.
I wreszcie singleton nie jest pozbawiony własnych wyzwań.
- Cytat z tego inspirującego przemówienia „Global State and Singletons”
PS: SqlConnection
Ten jest nieistotny dla obecnych pytań i odpowiedzi, ale prawdopodobnie jest dobrze znany. Wzorzec użycia SqlConnection jest inny. Państwo nie ma potrzeby ponownego użycia SqlConnection , ponieważ będzie ona obsługiwać jego puli połączeń lepiej.
Różnica wynika z ich podejścia do wdrożenia. Każda instancja HttpClient korzysta z własnej puli połączeń (cytowanej tutaj ); ale zgodnie z tym SqlConnection jest zarządzany przez centralną pulę połączeń .
I nadal musisz pozbyć się SqlConnection, tak jak powinieneś zrobić dla HttpClient.
źródło
Zrobiłem kilka testów, aby zobaczyć poprawę wydajności z static
HttpClient
. Do testów użyłem poniższego kodu:Dla testów:
Znalazłem poprawę wydajności od 40% do 60% uon przy użyciu statycznego
HttpClient
zamiast wysyłania go naHttpClient
żądanie. Umieściłem szczegóły wyniku testu wydajności w poście na blogu tutaj .źródło
Aby poprawnie zamknąć połączenie TCP , musimy ukończyć sekwencję pakietów FIN - FIN + ACK - ACK (podobnie jak SYN - SYN + ACK - ACK podczas otwierania połączenia TCP ). Jeśli po prostu wywołamy metodę .Close () (zwykle dzieje się, gdy pozbywa się HttpClient ), i nie czekamy, aż strona zdalna potwierdzi nasze zamknięcie żądania (za pomocą FIN + ACK), otrzymamy stan TIME_WAIT na lokalny port TCP, ponieważ zlikwidowaliśmy naszego odbiornika (HttpClient) i nigdy nie mieliśmy szansy zresetować stanu portu do odpowiedniego stanu zamkniętego, gdy zdalny uczestnik wyśle nam pakiet FIN + ACK.
Właściwym sposobem zamknięcia połączenia TCP byłoby wywołanie metody .Close () i oczekiwanie na zdarzenie close z drugiej strony (FIN + ACK) po naszej stronie. Tylko wtedy możemy wysłać ostateczne potwierdzenie i zlikwidować klienta HttpClient.
Aby dodać, sensowne jest utrzymywanie otwartych połączeń TCP, jeśli wykonujesz żądania HTTP, ze względu na nagłówek HTTP „Connection: Keep-Alive”. Co więcej, możesz poprosić zdalnego partnera, aby zamknął dla ciebie połączenie, ustawiając nagłówek HTTP „Connection: Close”. W ten sposób porty lokalne będą zawsze odpowiednio zamknięte, zamiast znajdować się w stanie TIME_WAIT.
źródło
Oto podstawowy klient API, który skutecznie wykorzystuje HttpClient i HttpClientHandler. Gdy tworzysz nowego klienta HttpClient w celu złożenia wniosku, wiąże się to z dużym nakładem pracy. NIE odtwarzaj ponownie HttpClient dla każdego żądania. Ponownie wykorzystaj HttpClient w jak największym stopniu ...
Stosowanie:
źródło
Nie ma jednego sposobu na użycie klasy HttpClient. Kluczem do sukcesu jest zaprojektowanie aplikacji w sposób odpowiedni dla środowiska i ograniczeń.
HTTP to świetny protokół do użycia, gdy trzeba ujawnić publiczne interfejsy API. Może być również skutecznie wykorzystywany w przypadku usług wewnętrznych o niskim opóźnieniu - chociaż wzór kolejki komunikatów RPC jest często lepszym wyborem dla usług wewnętrznych.
Wykonanie HTTP dobrze się komplikuje.
Rozważ następujące:
Ale przede wszystkim przetestuj, zmierz i potwierdź. Jeśli nie zachowuje się zgodnie z planem, możemy odpowiedzieć na konkretne pytania dotyczące sposobu osiągnięcia oczekiwanych rezultatów.
źródło
HttpClient
implementujeIDisposable
. Dlatego nie jest nierozsądne oczekiwać, że będzie to krótkotrwały obiekt, który umie po sobie posprzątać, odpowiedni do zawijaniausing
instrukcji za każdym razem, gdy jej potrzebujesz. Niestety tak nie działa. Wpis na blogu, do którego prowadzi OP, wyraźnie pokazuje, że istnieją zasoby (w szczególności połączenia przez gniazdo TCP), które istnieją długo po tym, jakusing
instrukcja wyszła poza zakres iHttpClient
obiekt został prawdopodobnie usunięty.