La programmation asynchrone "grandit" à travers la base de code. Il a été comparé à un virus zombie . La meilleure solution est de lui permettre de se développer, mais parfois ce n'est pas possible.
J'ai écrit quelques types dans ma bibliothèque Nito.AsyncEx pour gérer une base de code partiellement asynchrone. Il n'y a cependant pas de solution qui fonctionne dans toutes les situations.
Solution A
Si vous avez une méthode asynchrone simple qui n'a pas besoin de se synchroniser avec son contexte, vous pouvez utiliser Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Vous ne pas à utiliser Task.Wait
ou Task.Result
parce qu'ils enveloppent exceptions AggregateException
.
Cette solution n'est appropriée que si elle MyAsyncMethod
ne se synchronise pas avec son contexte. En d'autres termes, chaque await
entrée MyAsyncMethod
doit se terminer par ConfigureAwait(false)
. Cela signifie qu'il ne peut pas mettre à jour les éléments de l'interface utilisateur ni accéder au contexte de demande ASP.NET.
Solution B
Si vous MyAsyncMethod
devez vous synchroniser avec son contexte, vous pouvez utiliser AsyncContext.RunTask
pour fournir un contexte imbriqué:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* Mise à jour du 14/04/2014: dans les versions plus récentes de la bibliothèque, l'API est la suivante:
var result = AsyncContext.Run(MyAsyncMethod);
(Il est correct d'utiliser Task.Result
dans cet exemple car RunTask
propage des Task
exceptions).
La raison pour laquelle vous pourriez avoir besoin à la AsyncContext.RunTask
place Task.WaitAndUnwrapException
est à cause d'une possibilité de blocage plutôt subtile qui se produit sur WinForms / WPF / SL / ASP.NET:
- Une méthode synchrone appelle une méthode asynchrone, obtenant un
Task
.
- La méthode synchrone fait une attente de blocage sur le
Task
.
- La
async
méthode utilise await
sans ConfigureAwait
.
- Le
Task
ne peut pas terminer dans cette situation car il se termine uniquement lorsque la async
méthode est terminée; la async
méthode ne peut pas se terminer car elle tente de planifier sa continuation vers le SynchronizationContext
, et WinForms / WPF / SL / ASP.NET n'autorisera pas la poursuite à s'exécuter car la méthode synchrone s'exécute déjà dans ce contexte.
C'est une des raisons pour lesquelles c'est une bonne idée d'utiliser autant que possible ConfigureAwait(false)
dans chaque async
méthode.
Solution C
AsyncContext.RunTask
ne fonctionnera pas dans tous les scénarios. Par exemple, si la async
méthode attend quelque chose qui nécessite un événement d'interface utilisateur, vous bloquerez même avec le contexte imbriqué. Dans ce cas, vous pouvez démarrer la async
méthode sur le pool de threads:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Cependant, cette solution nécessite un MyAsyncMethod
qui fonctionnera dans le contexte du pool de threads. Il ne peut donc pas mettre à jour les éléments de l'interface utilisateur ni accéder au contexte de demande ASP.NET. Et dans ce cas, vous pouvez tout aussi bien compléter ConfigureAwait(false)
ses await
déclarations et utiliser la solution A.
Mise à jour, 2019-05-01: Les "pratiques les moins pires" actuelles sont dans un article MSDN ici .