Wywołanie asynchroniczne z await w HttpClient nigdy nie zwraca

96

Mam połączenie, które C#wykonuję z poziomu aplikacji Metro opartej na XAML na Win8 CP; wywołanie to po prostu trafia do usługi sieciowej i zwraca dane JSON.

HttpMessageHandler handler = new HttpClientHandler();

HttpClient httpClient = new HttpClient(handler);
httpClient.BaseAddress = new Uri("http://192.168.1.101/api/");

var result = await httpClient.GetStreamAsync("weeklyplan");
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(WeeklyPlanData[]));
return (WeeklyPlanData[])ser.ReadObject(result);

Zawiesza się w miejscu, awaitale wywołanie http w rzeczywistości powraca prawie natychmiast (potwierdzone przez skrzypek); to jest tak, jakby awaitbył ignorowany i po prostu wisi tam.

Zanim zapytasz - TAK - funkcja Private Network jest włączona.

Jakieś pomysły, dlaczego to się zawiesi?

keithwarren7
źródło
1
Jak nazywasz tę asyncmetodę? Czy to nie stanowi wyjątku?
svick

Odpowiedzi:

137

Sprawdź tę odpowiedź na moje pytanie, które wydaje się być bardzo podobne.

Coś do wypróbowania: zadzwoń ConfigureAwait(false)do zadania zwróconego przez GetStreamAsync(). Na przykład

var result = await httpClient.GetStreamAsync("weeklyplan")
                             .ConfigureAwait(continueOnCapturedContext:false);

To, czy jest to przydatne, zależy od tego, jak wywoływany jest powyższy kod - w moim przypadku wywołanie asyncmetody using Task.GetAwaiter().GetResult()spowodowało zawieszenie się kodu.

Dzieje się tak, ponieważ GetResult()blokuje bieżący wątek do momentu zakończenia zadania. Po zakończeniu zadania próbuje ponownie wejść do kontekstu wątku, w którym zostało uruchomione, ale nie może, ponieważ w tym kontekście istnieje już wątek, który jest blokowany przez wywołanie GetResult()... zakleszczenia!

W tym poście w witrynie MSDN szczegółowo omówiono, w jaki sposób .NET synchronizuje równoległe wątki - a odpowiedź udzielona na moje własne pytanie zawiera kilka najlepszych praktyk.

Benjamin Fox
źródło
12
Dzięki, prawie zrezygnowałem z async / await, zanim to zobaczyłem.
Den
4
Ja też! Dlaczego nie jest to lepiej udokumentowane?
Jeszcze
1
Czy to się stanie, jeśli nie ma kontekstu interfejsu użytkownika ani kontekstu ASP.NET?
machinarium
1
Świetna odpowiedź! Ale jestem zdezorientowany, dlaczego do tej pory mam ten problem tylko podczas korzystania z HttpClient, wydaje się, że podstawowa implementacja w HttpClient nie jest poprawnie zaimplementowana. Inne obejścia, które znalazłem, obejmują ustawienie bieżącego wątku jako STA, co pomaga, ale jest naprawdę pośrednie, zwłaszcza gdy używasz zestawu innej firmy i nie jesteś świadomy, że pod maską niektóre wywołania będą zdecydowanie czekać na odpowiedź, że to nigdy nie dostanie. W moim przypadku dll był w firmie, więc mogliśmy ConfigureAwait ... ale musieliśmy to zrobić na najniższym poziomie do obiektu HttpClient.
Chris Schaller
2
@ChrisSchaller Przeczytaj pełną odpowiedź na stackoverflow.com/a/10351400/174735 , która dość dokładnie wyjaśnia problem.
Benjamin Fox
5

Tylko ostrzeżenie - jeśli przegapisz oczekiwanie na najwyższym poziomie w kontrolerze ASP.NET i zwrócisz zadanie zamiast wyniku jako odpowiedź, w rzeczywistości po prostu zawiesza się w zagnieżdżonych wywołaniach oczekiwania bez błędów. Głupi błąd, ale gdybym zobaczył ten post, zaoszczędziłoby mi to trochę czasu na sprawdzaniu kodu pod kątem czegoś dziwnego.

dysza
źródło
0

Zastrzeżenie: nie podoba mi się rozwiązanie ConfigureAwait (), ponieważ wydaje mi się nieintuicyjne i trudne do zapamiętania. Zamiast tego doszedłem do wniosku, że nieoczekiwane wywołania metod zawijają w Task.Run (() => myAsyncMethodNotUsingAwait ()). Wydaje się, że działa to w 100%, ale może to być tylko stan wyścigu !? Nie jestem pewien, co się dzieje, szczerze mówiąc. Ten wniosek może być błędny i ryzykuję, że moje punkty StackOverflow tutaj, mam nadzieję, wyciągną wnioski z komentarzy :-P. Przeczytaj je!

Właśnie miałem problem zgodnie z opisem i znalazłem więcej informacji tutaj .

Instrukcja brzmi: „nie można wywołać metody asynchronicznej”

await asyncmethod2()

z metody, która blokuje

myAsyncMethod().Result

W moim przypadku nie mogłem zmienić metody wywoływania i nie było to asynchroniczne. Ale tak naprawdę nie dbałem o wynik. O ile pamiętam, nie działało również usuwanie .Result i brakowało czekania.

Więc zrobiłem to:

public void Configure()
{
    var data = "my data";
    Task.Run(() => NotifyApi(data));
}

private async Task NotifyApi(bool data)
{
    var toSend = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
    await client.PostAsync("http://...", data);
}

W moim przypadku nie przejmowałem się wynikiem w wywołaniu metody innej niż asynchroniczna, ale myślę, że jest to dość powszechne w tym przypadku użycia. Możesz użyć wyniku w wywołującej metodzie asynchronicznej.

CodingYourLife
źródło