HttpClient.GetAsync (...) nigdy nie powraca podczas używania wait/async
Edytuj: to pytanie wygląda na to, że może to być ten sam problem, ale nie ma odpowiedzi...
Edit: W przypadku testowym 5 zadanie wydaje się być zablokowane w stanie WaitingForActivation
.
Napotkałem dziwne zachowanie przy użyciu System. Net. Http. HttpClient w. NET 4.5 - gdzie "oczekiwanie" na wynik wywołania (np.) httpClient.GetAsync(...)
nigdy nie powróci.
Występuje to tylko w pewnych okolicznościach, gdy używa się nowej funkcjonalności języka async/wait I interfejsu API Zadań - kod wydaje się zawsze działać, gdy używa się tylko kontynuacji.
Oto kod, który odtwarza problem - wrzuć go do nowego "projektu MVC 4 WebApi" w Visual Studio 11, aby odsłonić 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 zwraca te same dane (nagłówki odpowiedzi z stackoverflow.com) z wyjątkiem /api/test5
, które nigdy się nie kończy.
Czy napotkałem błąd w klasie HttpClient, czy też nadużywam API w niektórych sposób?
Kod do odtworzenia:
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();
}
}
5 answers
Nadużywasz API.
Oto sytuacja: w ASP.NET, tylko jeden wątek może obsłużyć żądanie na raz. W razie potrzeby możesz przeprowadzić równoległe przetwarzanie (zapożyczenie dodatkowych wątków z puli wątków), ale tylko jeden wątek miałby kontekst żądania (dodatkowe wątki nie mają kontekstu żądania).
To jest zarządzane przez ASP.NET SynchronizationContext
.
Domyślnie, gdy await
A Task
, metoda wznawia się na przechwyconym SynchronizationContext
(lub uchwycony TaskScheduler
, Jeśli nie ma SynchronizationContext
). Normalnie, jest to właśnie to, czego chcesz: asynchroniczna akcja kontrolera spowoduje await
coś, a gdy zostanie wznowiona, zostanie wznowiona z kontekstem żądania.
Oto dlaczego test5
zawodzi:
-
Test5Controller.Get
wykonujeAsyncAwait_GetSomeDataAsync
(w ramach ASP.NET kontekst żądania). -
AsyncAwait_GetSomeDataAsync
wykonujeHttpClient.GetAsync
(w ramach ASP.NET kontekst żądania). - żądanie HTTP jest wysyłane i
HttpClient.GetAsync
zwraca nieukończoneTask
. -
AsyncAwait_GetSomeDataAsync
Task
; ponieważ nie jest kompletna,AsyncAwait_GetSomeDataAsync
zwraca nieukończonąTask
. -
Test5Controller.Get
blokuje bieżący wątek, ażTask
zakończy się. - pojawia się odpowiedź HTTP, a
Task
zwrócona przezHttpClient.GetAsync
jest zakończona. -
[[9]} próby wznowienia w ramach ASP.NET kontekst żądania. Jednak w tym kontekście istnieje już wątek: Wątek zablokowany w
Test5Controller.Get
. - impas.
Oto dlaczego pozostałe działają:
- (
test1
,test2
, oraztest3
):Continuations_GetSomeDataAsync
Na Zewnątrz ASP.NET kontekst żądania. Pozwala to na zakończenieTask
zwracanego przezContinuations_GetSomeDataAsync
bez konieczności ponownego wprowadzania kontekstu żądania. - (
test4
itest6
): ponieważTask
jest , ASP.NET wątek żądania nie jest blokowany. To pozwalaAsyncAwait_GetSomeDataAsync
używać ASP.NET poproś o kontekst, gdy jest gotowy do kontynuowania.
- W metody "biblioteki"
async
, używajConfigureAwait(false)
, gdy tylko jest to możliwe. W Twoim przypadku zmieni toAsyncAwait_GetSomeDataAsync
navar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
- Nie blokuj
Task
s; jestasync
aż do dołu. Innymi słowy, użyjawait
zamiastGetResult
(Task.Result
iTask.Wait
należy również zastąpićawait
).
W ten sposób otrzymujesz obie korzyści: kontynuacja (pozostała część metody AsyncAwait_GetSomeDataAsync
) jest uruchamiana na podstawowym wątku puli wątków, który nie musi wchodzić w ASP.NET kontekst żądania; oraz administrator sama w sobie to async
(co nie blokuje wątku żądania).
Więcej informacji:
- mój
async
/await
post intro , który zawiera krótki opis jakTask
używaćSynchronizationContext
. - asynchroniczne / oczekujące FAQ , które szczegółowo omawiają konteksty. Zobacz także oczekiwanie, interfejs i deadlocks! O rany! które stosuje się tutaj, nawet jeśli jesteś w ASP.NET zamiast interfejsu użytkownika, ponieważ ASP.NET
SynchronizationContext
ogranicza żądanie kontekst do tylko jednego wątku na raz. - This MSDN forum post .
- Stephen Toub Demos tego impasu (przy użyciu interfejsu użytkownika) i podobnie Lucjan Wischik .
Aktualizacja 2012-07-13: włączone tę odpowiedź do postu na blogu .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-28 13:39:42
Edit: generalnie staraj się unikać wykonywania poniższych czynności, z wyjątkiem ostatniego wysiłku rowu, aby uniknąć impasu. Przeczytaj pierwszy komentarz Stephena Cleary ' ego.
Szybkie poprawki z tutaj . Zamiast pisać:
Task tsk = AsyncOperation();
tsk.Wait();
Try:
Task.Run(() => AsyncOperation()).Wait();
Lub jeśli potrzebujesz wyniku:
var result = Task.Run(() => AsyncOperation()).Result;
Ze źródła (edytowane w celu dopasowania do powyższego przykładu):
Asynchroniczna operacja będzie teraz wywoływana w ThreadPool, gdzie nie będzie synchronizacją, a kontynuacje używane wewnątrz AsyncOperation nie będzie wymuszane z powrotem do wątku wywołującego.
Dla mnie wygląda to na użyteczną opcję, ponieważ nie mam opcji, aby uczynić ją asynchroniczną do końca (co wolałbym).
Ze źródła:
Upewnij się, że oczekiwanie w metodzie FooAsync nie znajduje kontekstu do szeryf wrócił do. Najprostszym sposobem na to jest wywołanie asynchronicznej pracy z ThreadPool, np. poprzez owinięcie inwokacja w zadaniu.Bieg, np.
Int Sync() { zadanie zwrotne.Run (() = > Library.FooAsync ()).Wynik;}
FooAsync będzie teraz wywoływany w ThreadPool, gdzie nie będzie SynchronizationContext i kontynuacje używane wewnątrz FooAsync nie zostanie wymuszony powrót do wątku wywołującego sync ().
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-15 10:07:41
Ponieważ używasz .Result
lub .Wait
lub await
, spowoduje toimpas w Twoim kodzie.
Możesz użyć ConfigureAwait(false)
w async
metod zapobiegania impas
Tak:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Możesz używać
ConfigureAwait(false)
wszędzie tam, gdzie jest to możliwe, aby nie blokować kodu asynchronicznego .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-20 15:12:52
Te dwie szkoły nie są tak naprawdę wykluczające.
Oto scenariusz, w którym po prostu musisz użyć
Task.Run(() => AsyncOperation()).Wait();
Lub coś w rodzaju
AsyncContext.Run(AsyncOperation);
Mam akcję MVC, która znajduje się pod atrybutem transakcji bazy danych. Pomysł polegał (prawdopodobnie) na cofnięciu wszystkiego, co zostało zrobione w akcji, jeśli coś pójdzie nie tak. Nie pozwala to na przełączanie kontekstu, w przeciwnym razie wycofanie transakcji lub zatwierdzenie samo się nie powiedzie.
Biblioteka, której potrzebuję, jest asynchroniczna, ponieważ ma działać async.
Jedyna opcja. Uruchom to jako normalne połączenie synchronizacji.
Mówię tylko, że każdy ma swoje.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-17 17:18:32
Szukam tutaj:
Http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs. 110).aspx
I tutaj:
I widząc:
Ten typ i jego członkowie są przeznaczone do użytku przez kompilator.
Biorąc pod uwagę, że wersja await
działa i jest "właściwym" sposobem robienia rzeczy, czy naprawdę potrzebujesz odpowiedzi na to pytanie?
Mój głos to: nadużywanie API.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-27 01:57:05