Edit: Cette question semble avoir le même problème, mais n’a pas de réponse …
Edit: Dans le WaitingForActivation
test 5, la tâche semble être bloquée dans l’état WaitingForActivation
.
J’ai rencontré certains comportements bizarres en utilisant System.Net.Http.HttpClient dans .NET 4.5 – où “attendre” le résultat d’un appel à (par exemple) httpClient.GetAsync(...)
ne reviendra jamais.
Cela ne se produit que dans certaines circonstances lorsque vous utilisez la nouvelle fonctionnalité de langage asynchrone / wait et l’API Tasks – le code semble toujours fonctionner lorsque vous utilisez uniquement des continuations.
Voici un code qui reproduit le problème: déposez-le dans un nouveau projet “MVC 4 WebApi” dans Visual Studio 11 pour exposer les points de terminaison GET suivants:
/api/test1 /api/test2 /api/test3 /api/test4 /api/test5 <--- never completes /api/test6
Chacun des points de terminaison renvoie ici les mêmes données (les en-têtes de réponse de stackoverflow.com), à l’exception de /api/test5
qui ne se termine jamais.
Code à reproduire:
public class BaseApiController : ApiController { /// /// Resortingeves data using continuations /// protected Task Continuations_GetSomeDataAsync() { var httpClient = new HttpClient(); var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead); return t.ContinueWith(t1 => t1.Result.Content.Headers.ToSsortingng()); } /// /// Resortingeves data using async/await /// protected async Task AsyncAwait_GetSomeDataAsync() { var httpClient = new HttpClient(); var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead); return result.Content.Headers.ToSsortingng(); } } public class Test1Controller : BaseApiController { /// /// Handles task using Async/Await /// public async Task Get() { var data = await Continuations_GetSomeDataAsync(); return data; } } public class Test2Controller : BaseApiController { /// /// Handles task by blocking the thread until the task completes /// public ssortingng Get() { var task = Continuations_GetSomeDataAsync(); var data = task.GetAwaiter().GetResult(); return data; } } public class Test3Controller : BaseApiController { /// /// Passes the task back to the controller host /// public Task Get() { return Continuations_GetSomeDataAsync(); } } public class Test4Controller : BaseApiController { /// /// Handles task using Async/Await /// public async Task Get() { var data = await AsyncAwait_GetSomeDataAsync(); return data; } } public class Test5Controller : BaseApiController { /// /// Handles task by blocking the thread until the task completes /// public ssortingng Get() { var task = AsyncAwait_GetSomeDataAsync(); var data = task.GetAwaiter().GetResult(); return data; } } public class Test6Controller : BaseApiController { /// /// Passes the task back to the controller host /// public Task Get() { return AsyncAwait_GetSomeDataAsync(); } }
Vous utilisez mal l’API.
Voici la situation: dans ASP.NET, un seul thread peut gérer une requête à la fois. Vous pouvez effectuer un parallel processing si nécessaire (en empruntant des threads supplémentaires dans le pool de threads), mais un seul thread aurait le contexte de requête (les threads supplémentaires n’ont pas le contexte de requête).
Ceci est géré par ASP.NET SynchronizationContext
.
Par défaut, lorsque vous await
une Task
, la méthode reprend sur un SynchronizationContext
capturé (ou un TaskScheduler
capturé, s’il n’y a pas de SynchronizationContext
). Normalement, c’est exactement ce que vous voulez: une action de contrôleur asynchrone await
quelque chose, et quand elle reprend, elle reprend le contexte de la requête.
Donc, voici pourquoi test5
échoue:
Test5Controller.Get
exécute AsyncAwait_GetSomeDataAsync
(dans le contexte de la requête ASP.NET). AsyncAwait_GetSomeDataAsync
exécute HttpClient.GetAsync
(dans le contexte de la demande ASP.NET). HttpClient.GetAsync
renvoie une Task
incomplète. AsyncAwait_GetSomeDataAsync
attend la Task
; comme il n’est pas complet, AsyncAwait_GetSomeDataAsync
renvoie une Task
incomplète. Test5Controller.Get
bloque le thread en cours jusqu’à la fin de cette Task
. Task
renvoyée par HttpClient.GetAsync
est terminée. AsyncAwait_GetSomeDataAsync
tente de reprendre dans le contexte de la requête ASP.NET. Cependant, il existe déjà un thread dans ce contexte: le thread bloqué dans Test5Controller.Get
. Voici pourquoi les autres fonctionnent:
test1
, test2
et test3
): Continuations_GetSomeDataAsync
planifie la poursuite du pool de threads, en dehors du contexte de la requête ASP.NET. Cela permet à la Task
renvoyée par Continuations_GetSomeDataAsync
de se terminer sans avoir à ressaisir le contexte de la demande. test6
et test6
): Comme la Task
est attendue , le thread de demande ASP.NET n’est pas bloqué. Cela permet à AsyncAwait_GetSomeDataAsync
d’utiliser le contexte de requête ASP.NET lorsqu’il est prêt à continuer. Et voici les meilleures pratiques:
async
“library”, utilisez ConfigureAwait(false)
chaque fois que possible. Dans votre cas, cela changerait AsyncAwait_GetSomeDataAsync
pour var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
s; c’est async
tout en bas. En d’autres termes, utilisez GetResult
au lieu de GetResult
( Task.Result
et Task.Wait
doivent également être remplacés par await
). De cette façon, vous obtenez les deux avantages: la suite (le rest de la méthode AsyncAwait_GetSomeDataAsync
) est exécutée sur un thread de pool de threads de base qui ne doit pas entrer dans le contexte de la requête ASP.NET; et le contrôleur lui-même est async
(qui ne bloque pas un thread de demande).
Plus d’information:
async
/ waiting , qui comprend une brève description de la manière dont les serveurs de Task
utilisent SynchronizationContext
. SynchronizationContext
restreint le contexte de la requête à un seul thread à la fois. Mise à jour 2012-07-13: Incorporer cette réponse dans un article de blog .
Modifier: essayez généralement d’éviter de faire les opérations ci-dessous, sauf en dernier recours pour éviter les blocages. Lisez le premier commentaire de Stephen Cleary.
Solution rapide d’ ici . Au lieu d’écrire:
Task tsk = AsyncOperation(); tsk.Wait();
Essayer:
Task.Run(() => AsyncOperation()).Wait();
Ou si vous avez besoin d’un résultat:
var result = Task.Run(() => AsyncOperation()).Result;
De la source (édité pour correspondre à l’exemple ci-dessus):
AsyncOperation va maintenant être appelé sur le ThreadPool, où il n’y aura pas de SynchronizationContext, et les continuations utilisées dans AsyncOperation ne seront pas renvoyées au thread appelant.
Pour moi, cela ressemble à une option utilisable car je n’ai pas la possibilité de la rendre asynchrone jusqu’à la fin (ce que je préférerais).
De la source:
Assurez-vous que l’attente dans la méthode FooAsync ne trouve pas de contexte à regrouper. La méthode la plus simple consiste à appeler le travail asynchrone à partir de ThreadPool, par exemple en encapsulant l’appel dans un Task.Run, par exemple
int Sync () {return Task.Run (() => Library.FooAsync ()). Résultat; }
FooAsync va maintenant être appelé sur le ThreadPool, où il n’y aura pas de SynchronizationContext, et les continuations utilisées dans FooAsync ne seront pas renvoyées au thread qui appelle Sync ().
Puisque vous utilisez .Result
ou .Wait
ou await
ceci finira par provoquer un blocage dans votre code.
vous pouvez utiliser ConfigureAwait(false)
dans les méthodes async
pour empêcher le blocage
comme ça:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Vous pouvez utiliser
ConfigureAwait(false)
mesure du possible pour Don’t Block Async Code.
Ces deux écoles n’excluent pas vraiment.
Voici le scénario où vous devez simplement utiliser
Task.Run(() => AsyncOperation()).Wait();
ou quelque chose comme
AsyncContext.Run(AsyncOperation);
J’ai une action MVC sous l’atsortingbut de transaction de firebase database. L’idée était (probablement) de tout annuler dans l’action si quelque chose se passe mal. Cela ne permet pas la commutation de contexte, sinon la restauration ou la validation de la transaction va échouer.
La bibliothèque dont j’ai besoin est asynchrone car elle est censée exécuter un asynchrone.
La seule option. Exécutez-le comme un appel de synchronisation normal.
Je dis juste à chacun son propre.
Je regarde ici:
http://msdn.microsoft.com/en-us/library/system.runtime.comstackrservices.taskawaiter(v=vs.110).aspx
Et ici:
Et en voyant:
Ce type et ses membres sont destinés à être utilisés par le compilateur.
Considérant que la version d’ await
fonctionne, et est la bonne façon de faire les choses, avez-vous vraiment besoin d’une réponse à cette question?
Mon vote est le suivant: mal utiliser l’API .