Edit: To pytanie wygląda to może być ten sam problem, ale nie ma odpowiedzi ...
Edycja: W przypadku testowym 5 zadanie wydaje się być zablokowane WaitingForActivation
.
Napotkałem pewne dziwne zachowanie podczas używania System.Net.Http.HttpClient w .NET 4.5 - gdzie „oczekiwanie” na wynik wywołania (np.) httpClient.GetAsync(...)
Nigdy nie powróci.
Dzieje się tak tylko w niektórych okolicznościach, gdy używana jest nowa funkcja języka async / czekaj i API zadań - kod zawsze wydaje się działać, gdy używa się tylko kontynuacji.
Oto kod, który odtwarza problem - upuść go w nowym „projekcie MVC 4 WebApi” w Visual Studio 11, aby ujawnić następujące punkty końcowe GET:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
Każdy z punktów końcowych tutaj zwraca te same dane (nagłówki odpowiedzi z stackoverflow.com) z wyjątkiem /api/test5
których nigdy się nie kończy.
Czy napotkałem błąd w klasie HttpClient, czy też niewłaściwie korzystam z interfejsu API?
Kod do reprodukcji:
public class BaseApiController : ApiController
{
/// <summary>
/// Retrieves data using continuations
/// </summary>
protected Task<string> Continuations_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
}
/// <summary>
/// Retrieves data using async/await
/// </summary>
protected async Task<string> AsyncAwait_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return result.Content.Headers.ToString();
}
}
public class Test1Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await Continuations_GetSomeDataAsync();
return data;
}
}
public class Test2Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = Continuations_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test3Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return Continuations_GetSomeDataAsync();
}
}
public class Test4Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await AsyncAwait_GetSomeDataAsync();
return data;
}
}
public class Test5Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = AsyncAwait_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test6Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return AsyncAwait_GetSomeDataAsync();
}
}
źródło
HttpClient.GetAsync(...)
?Odpowiedzi:
Nadużywasz interfejsu API.
Oto sytuacja: w ASP.NET tylko jeden wątek może obsłużyć żądanie jednocześnie. W razie potrzeby możesz wykonać równoległe przetwarzanie (pożyczanie dodatkowych wątków z puli wątków), ale tylko jeden wątek będzie miał kontekst żądania (dodatkowe wątki nie mają kontekstu żądania).
Jest zarządzany przez ASP.NET
SynchronizationContext
.Domyślnie, gdy jesteś
await
aTask
, metoda wznawia przechwyconeSynchronizationContext
(lub przechwyconeTaskScheduler
, jeśli nie maSynchronizationContext
). Zwykle jest to dokładnie to, czego chcesz: asynchroniczna czynność kontroleraawait
coś zrobi , a po wznowieniu wznawia z kontekstem żądania.Oto dlaczego
test5
zawodzi:Test5Controller.Get
wykonujeAsyncAwait_GetSomeDataAsync
(w kontekście żądania ASP.NET).AsyncAwait_GetSomeDataAsync
wykonujeHttpClient.GetAsync
(w kontekście żądania ASP.NET).HttpClient.GetAsync
zwraca niezakończoneTask
.AsyncAwait_GetSomeDataAsync
czeka naTask
; ponieważ nie jest kompletny,AsyncAwait_GetSomeDataAsync
zwraca niedokończoneTask
.Test5Controller.Get
blokuje bieżący wątek do tego czasuTask
zakończy.Task
zwracana jest przezHttpClient.GetAsync
jest zakończone.AsyncAwait_GetSomeDataAsync
próbuje wznowić w kontekście żądania ASP.NET. Jednak w tym kontekście jest już wątek: wątek został zablokowanyTest5Controller.Get
.Oto dlaczego pozostałe działają:
test1
,test2
itest3
):Continuations_GetSomeDataAsync
planuje kontynuację puli wątków poza kontekstem żądania ASP.NET. To pozwalaTask
zwrotu przezContinuations_GetSomeDataAsync
bez konieczności ponownego wprowadzania kontekstu żądania.test4
itest6
): Ponieważ oczekiwanyTask
jest , wątek żądania ASP.NET nie jest blokowany. To pozwalaAsyncAwait_GetSomeDataAsync
to użycie kontekstu żądania ASP.NET, gdy jest on gotowy do kontynuowania.A oto najlepsze praktyki:
async
metodach „bibliotecznych” używaj,ConfigureAwait(false)
gdy tylko jest to możliwe. W twoim przypadku to się zmieniAsyncAwait_GetSomeDataAsync
navar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
s; jest jużasync
na dole. Innymi słowy, użyjawait
zamiastGetResult
(Task.Result
iTask.Wait
należy je również zastąpićawait
).W ten sposób uzyskuje się obie korzyści: kontynuacja (pozostała część
AsyncAwait_GetSomeDataAsync
metody) jest uruchamiana w podstawowym wątku puli wątków, który nie musi wchodzić w kontekst żądania ASP.NET; a sam kontroler toasync
(który nie blokuje wątku żądania).Więcej informacji:
async
/await
intro post , który zawiera krótki opis korzystania zTask
oczekującychSynchronizationContext
.SynchronizationContext
ogranicza kontekst żądania do tylko jednego wątku na raz.Aktualizacja 2012-07-13: Włączono tę odpowiedź do postu na blogu .
źródło
SynchroniztaionContext
która wyjaśnia, że dla danego żądania może istnieć tylko jeden wątek? Jeśli nie, myślę, że tak powinno być.SynchronizationContext
zapewnia pewne ważne funkcje: przepływa kontekst żądania. Obejmuje to wszelkiego rodzaju rzeczy, od uwierzytelniania przez pliki cookie po kulturę. W ASP.NET zamiast synchronizować z powrotem do interfejsu użytkownika, synchronizujesz z powrotem w kontekście żądania. Może się to wkrótce zmienić: nowyApiController
maHttpRequestMessage
kontekst jako właściwość - więc przepuszczanie kontekstu może nie być wymaganeSynchronizationContext
- ale jeszcze tego nie wiem.Edycja: Generalnie staraj się unikać wykonywania poniższych czynności, z wyjątkiem ostatniego wysiłku w celu uniknięcia impasu. Przeczytaj pierwszy komentarz Stephena Cleary'ego.
Szybka naprawa stąd . Zamiast pisać:
Próbować:
Lub jeśli potrzebujesz wyniku:
Ze źródła (edytowanego w celu dopasowania do powyższego przykładu):
Dla mnie wygląda to na użyteczną opcję, ponieważ nie mam opcji, aby całkowicie ją asynchronizować (co wolałbym).
Ze źródła:
źródło
async
kodu w ASP.NET i w rzeczywistości może powodować problemy na dużą skalę. BTW,ConfigureAwait
nie „przerywa właściwego zachowania asynchronicznego” w żadnym scenariuszu; dokładnie tego powinieneś używać w kodzie biblioteki.Avoid Exposing Synchronous Wrappers for Asynchronous Implementations
. Cała reszta postu wyjaśnia kilka różnych sposobów na zrobienie tego, jeśli jest to absolutnie konieczne .Ponieważ używasz
.Result
lub.Wait
lubawait
spowoduje to zakleszczenie w kodzie.można użyć
ConfigureAwait(false)
wasync
metodach zapobiegania impasulubię to:
źródło
Te dwie szkoły tak naprawdę nie wykluczają.
Oto scenariusz, w którym musisz po prostu użyć
lub coś w tym rodzaju
Mam działanie MVC, które jest objęte atrybutem transakcji bazy danych. Pomysł polegał (prawdopodobnie) na wycofaniu wszystkiego, co zrobiono w akcji, jeśli coś pójdzie nie tak. Nie pozwala to na przełączanie kontekstu, w przeciwnym razie wycofanie transakcji lub zatwierdzenie się nie powiedzie.
Potrzebna mi biblioteka jest asynchroniczna, ponieważ oczekuje się, że będzie działać asynchronicznie.
Jedyna opcja. Uruchom jako zwykłe połączenie synchronizacji.
Po prostu mówię każdemu.
źródło
Zamieszczę to tutaj bardziej dla kompletności niż dla bezpośredniego związku z PO. Spędziłem prawie dzień debugując
HttpClient
zapytanie, zastanawiając się, dlaczego nigdy nie otrzymałem odpowiedzi.W końcu okazało się, że zapomniał
await
oasync
zaproszeniu dalej w dół stosu wywołań.Czuje się tak dobrze, jak brak średnika.
źródło
Szukam tutaj:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
I tu:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx
I widząc:
Biorąc pod uwagę, że
await
wersja działa i czy jest to „właściwy” sposób robienia rzeczy, czy naprawdę potrzebujesz odpowiedzi na to pytanie?Mój głos brzmi: nadużycie interfejsu API .
źródło
Test5Controller.Get()
aby wyeliminować oczekującego, wykonując następujące czynności:var task = AsyncAwait_GetSomeDataAsync(); return task.Result;
To samo zachowanie można zaobserwować.