2016-04-21 13 views
7

Krótkie pytanie:
Czy ktoś napotkał problem przy użyciu pojedynczego .NET HttpClient, w którym aplikacja przywiązuje procesor do 100%, dopóki nie zostanie ponownie uruchomiony?.NET: 100% użycie procesora w HttpClient ze względu na słownik?

Szczegóły:
biegnę usługa systemu Windows, który wykonuje ciągłą, ETL w oparciu o harmonogram. Jeden z wątków synchronizujących dane od czasu do czasu po prostu umiera lub zaczyna tracić kontrolę i mocuje procesor w 100%.

Miałem szczęście zobaczyć, że dzieje się to na żywo, zanim ktoś po prostu ponownie uruchomił usługę (standardowa poprawka) i był w stanie pobrać plik zrzutu.

Ładowanie tego w WinDbg (w/SOS i SOSEX), Stwierdziłem, że mam około 15 wątków (podzadania głównego wątku przetwarzania), wszystkie działające z identycznymi śladami stosu. Jednak nie pojawiają się żadne zakleszczenia. TO ZNACZY. wątki o wysokim stopniu wykorzystania działają, ale nigdy nie kończą.

Rzeczony segment stosu ślad następująco (adresy pominięta):

System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].FindEntry(System.__Canon) 
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].TryGetValue(System.__Canon, System.__Canon ByRef) 
System.Net.Http.Headers.HttpHeaders.ContainsParsedValue(System.String, System.Object) 
System.Net.Http.Headers.HttpGeneralHeaders.get_TransferEncodingChunked() 
System.Net.Http.Headers.HttpGeneralHeaders.AddSpecialsFrom(System.Net.Http.Headers.HttpGeneralHeaders) 
System.Net.Http.Headers.HttpRequestHeaders.AddHeaders(System.Net.Http.Headers.HttpHeaders) 
System.Net.Http.HttpClient.SendAsync(System.Net.Http.HttpRequestMessage, System.Net.Http.HttpCompletionOption, System.Threading.CancellationToken) 
... 
[Our Application Code] 

Według this article (i innych Znalazłem), wykorzystanie słowników jest nie pętle wątku bezpieczny i nieskończone są możliwe (podobnie jak zwykłe awarie), jeśli uzyskujesz dostęp do słownika w sposób wielowątkowy.

ALE Nasz kod aplikacji nie używa jawnie słownika. Więc gdzie jest słownik wspomniany w śledzeniu stosu?

Po przejściu przez .NET Reflector, to pojawia się, który HttpClient używa słownika do przechowywania wartości, które zostały skonfigurowane we właściwości "DefaultRequestHeaders". Każde żądanie, które otrzyma od HttpClient, wywołuje wyliczenie pojedynczego, niezawierającego wątków słownika (w celu dodania domyślnych nagłówków do żądania), które potencjalnie mogłoby w nieskończoność obrócić (lub zabić) wątki, jeśli pojawia się korupcja.

Firma Microsoft stwierdziła bez ogródek, że klasa HttpClient jest wątkowo bezpieczna. Ale wydaje mi się, że nie jest to już prawdą, jeśli jakiekolwiek nagłówki zostały dodane do DefaultRequestHeaders z HttpClient.

Moja analiza wydaje się wskazywać, że jest to prawdziwy problem root, a łatwym rozwiązaniem jest po prostu nigdy nie używać DefaultRequestHeaders, gdzie HttpClient może być używany w sposób wielowątkowy.

Jednak szukam jakiegoś potwierdzenia, że ​​nie szczerzę niewłaściwego drzewa. Jeśli jest to poprawne, wydaje się, że jest to błąd w strukturze .NET, który automatycznie mam wątpliwości.

Przykro nam z powodu ważnego pytania, ale dziękuję za wszelkie uwagi, które możesz wnieść.

+0

to jest ich standardowy moduł z zabezpieczeniem wątku: "Dowolne publiczne statyczne (udostępnione w języku Visual Basic) elementy tego typu są bezpieczne dla wątków. Żadne elementy instancji nie mają gwarancji, że są bezpieczne dla wątków." –

+0

proszę podać [mcve] –

+1

Dlaczego klient Http jest singletonem? Wydaje mi się, że najłatwiejszą opcją byłoby po prostu pozwolić każdemu wątkowi utworzyć i użyć własnej instancji. – pquest

Odpowiedz

1

Dzięki za wszystkie komentarze; sprawili, że myślałem różnymi liniami i pomogli mi znaleźć ostateczną przyczynę problemu.

Choć kwestia był wynikiem korupcji w słowniku podkładowej z DefaultRequestHeaders, prawdziwym winowajcą był kod inicjalizacji dla obiektu httpclient:

private HttpClient InitializeClient() 
{ 
    if (_client == null) 
    { 
     _client = GetHttpClient(); 
     _client.DefaultRequestHeaders.Accept.Clear(); 
     _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 
     SetBaseAddress(BaseAddress); 
    } 
    return _client; 
} 

powiedziałem, że HttpClient była pojedyncza, który jest częściowo niepoprawny. Jest tworzony jako pojedyncza instancja, która jest współdzielona przez wiele wątków, tworząc jednostkę pracy i jest usuwana po zakończeniu pracy. Nowa instancja zostanie odwirowana przy następnym wykonaniu tego określonego zadania.

Powyższa metoda "InitializeClient" jest wywoływana za każdym razem, gdy żądanie ma zostać wysłane, i powinna po prostu spowodować zwarcie, ponieważ pole "_client" nie jest zerowe po pierwszym prześwicie.

(Należy zauważyć, że nie jest to robione w konstruktorze obiektu, ponieważ jest to klasa abstrakcyjna, a "GetHttpClient" jest metodą abstrakcyjną - BTW: nigdy nie wywołuj abstrakcyjnej metody w konstruktorze klasy podstawowej. .. który powoduje inne koszmary)

Oczywiście, jest to dość oczywiste, że nie jest to bezpieczne dla wątków, a wynikłe zachowanie jest niedeterministyczne.

Poprawka polega na umieszczeniu tego kodu w podwójnie sprawdzanej instrukcji "lock" (chociaż i tak wyeliminuję użycie właściwości "DefaultRequestHeaders", tylko dlatego, że).

W dużym skrócie, moje oryginalne pytanie nie powinno być problemem, jeśli zachowacie ostrożność w inicjowaniu HttpClient.

Dzięki za jasność myśli, którą wszyscy zapewnili!

+0

To jest klasyczny problem z gwintowaniem. Tabela mieszająca staje się cykliczna ze względu na rasy. Nieskończona kolizja, że ​​tak powiem. Nie widziałem twojego pytania, a ja bym odpowiedział. – usr

+0

@usr: Bez obaw; Cieszę się, że otrzymałem dobre wyjaśnienie co do przyczyny. :) – Greenknight

Powiązane problemy