J'ai une public async void Foo()
méthode que je veux appeler à partir d'une méthode synchrone. Jusqu'à présent, tout ce que j'ai vu de la documentation MSDN appelle des méthodes asynchrones via des méthodes asynchrones, mais tout mon programme n'est pas construit avec des méthodes asynchrones.
Est-ce seulement possible?
Voici un exemple d'appel de ces méthodes à partir d'une méthode asynchrone: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
Maintenant, je cherche à appeler ces méthodes asynchrones à partir de méthodes de synchronisation.
c#
async-await
La tour
la source
la source
async void Foo()
méthode ne retourne pasTask
cela signifie qu'un appelant ne peut pas savoir quand elle se termine, elle doit retourner à laTask
place.Réponses:
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
:Vous ne pas à utiliser
Task.Wait
ouTask.Result
parce qu'ils enveloppent exceptionsAggregateException
.Cette solution n'est appropriée que si elle
MyAsyncMethod
ne se synchronise pas avec son contexte. En d'autres termes, chaqueawait
entréeMyAsyncMethod
doit se terminer parConfigureAwait(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 utiliserAsyncContext.RunTask
pour fournir un contexte imbriqué:* Mise à jour du 14/04/2014: dans les versions plus récentes de la bibliothèque, l'API est la suivante:
(Il est correct d'utiliser
Task.Result
dans cet exemple carRunTask
propage desTask
exceptions).La raison pour laquelle vous pourriez avoir besoin à la
AsyncContext.RunTask
placeTask.WaitAndUnwrapException
est à cause d'une possibilité de blocage plutôt subtile qui se produit sur WinForms / WPF / SL / ASP.NET:Task
.Task
.async
méthode utiliseawait
sansConfigureAwait
.Task
ne peut pas terminer dans cette situation car il se termine uniquement lorsque laasync
méthode est terminée; laasync
méthode ne peut pas se terminer car elle tente de planifier sa continuation vers leSynchronizationContext
, 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 chaqueasync
méthode.Solution C
AsyncContext.RunTask
ne fonctionnera pas dans tous les scénarios. Par exemple, si laasync
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 laasync
méthode sur le pool de threads: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éterConfigureAwait(false)
sesawait
déclarations et utiliser la solution A.Mise à jour, 2019-05-01: Les "pratiques les moins pires" actuelles sont dans un article MSDN ici .
la source
WaitAndUnwrapException
est ma propre méthode de ma bibliothèque AsyncEx . Les bibliothèques officielles .NET ne fournissent pas beaucoup d'aide pour mélanger le code de synchronisation et asynchrone (et en général, vous ne devriez pas le faire!). J'attends RTW .NET 4.5 et un nouvel ordinateur portable non XP avant de mettre à jour AsyncEx pour qu'il fonctionne sur 4.5 (je ne peux pas actuellement développer pour 4.5 car je suis bloqué sur XP pendant quelques semaines de plus).AsyncContext
a maintenant uneRun
méthode qui prend une expression lambda, vous devez donc utiliservar result = AsyncContext.Run(() => MyAsyncMethod());
RunTask
méthode. La chose la plus proche que j'ai pu trouver étaitRun
, mais cela n'a pas deResult
propriété.var result = AsyncContext.Run(MyAsyncMethod);
L'ajout d'une solution qui a finalement résolu mon problème permet, je l'espère, de gagner du temps à quelqu'un.
Lisez d'abord quelques articles de Stephen Cleary :
Parmi les "deux meilleures pratiques" dans "Ne pas bloquer sur le code asynchrone", la première ne fonctionnait pas pour moi et la seconde n'était pas applicable (essentiellement si je peux utiliser
await
, je le fais!).Voici donc ma solution de contournement: envelopper l'appel dans un
Task.Run<>(async () => await FunctionAsync());
et, espérons-le, plus aucune impasse .Voici mon code:
la source
Task.Run()
n'est pas une bonne pratique dans un code asynchrone. Mais, encore une fois, quelle est la réponse à la question d'origine? Vous n'appelez jamais une méthode asynchrone de manière synchrone? Nous souhaitons, mais dans un monde réel, parfois nous devons.Parallel.ForEach
abus n'auraient pas d'effet dans «le monde réel» et finalement, ils ont arrêté les serveurs. Ce code est OK pour les applications de la console mais, comme le dit @ChrisPratt, ne doit pas être utilisé dans les applications Web. Cela peut fonctionner "maintenant" mais n'est pas évolutif.Microsoft a créé une classe AsyncHelper (interne) pour exécuter Async en tant que Sync. La source ressemble à:
Les classes de base Microsoft.AspNet.Identity n'ont que des méthodes Async et pour les appeler Sync, il existe des classes avec des méthodes d'extension qui ressemblent à (exemple d'utilisation):
Pour ceux qui sont préoccupés par les termes du code de licence, voici un lien vers un code très similaire (ajoute simplement la prise en charge de la culture sur le fil) qui a des commentaires pour indiquer qu'il est sous licence MIT par Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
la source
await
appels avecConfigureAwait(false)
. J'ai essayéAsyncHelper.RunSync
d'appeler une fonction asynchrone à partir de laApplication_Start()
fonction dans Global.asax et cela semble fonctionner. Est-ce à dire que ceAsyncHelper.RunSync
n'est sûrement pas sujette au problème de blocage "retour au contexte de l'appelant" que j'ai lu ailleurs dans cet article?async Main fait maintenant partie de C # 7.2 et peut être activé dans les paramètres de construction avancés du projet.
Pour C # <7.2, la bonne façon est:
Vous verrez cela utilisé dans de nombreuses documentations Microsoft, par exemple: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- sujets-abonnements
la source
MainAsync().Wait()
?Vous lisez le mot-clé «attendre» comme «démarrer cette tâche longue, puis retourner le contrôle à la méthode appelante». Une fois la tâche de longue durée terminée, il exécute ensuite le code. Le code après l'attente est similaire à ce qui était auparavant des méthodes CallBack. La grande différence étant que le flux logique n'est pas interrompu, ce qui facilite grandement l'écriture et la lecture.
la source
Wait
encapsule les exceptions et a la possibilité d'un blocage.await
, elle serait exécutée de manière synchrone. Au moins, cela fonctionne pour moi (sans appelermyTask.Wait
). En fait, j'ai eu une exception quand j'ai essayé d'appelermyTask.RunSynchronously()
car elle avait déjà été exécutée!.Result
.Result
appel à ce moment-là, il n'y arrive donc jamais. EtResult
ne finit jamais, car il attend quelqu'un qui attend laResult
fin, essentiellement: DJe ne suis pas sûr à 100%, mais je pense que la technique décrite dans ce blog devrait fonctionner dans de nombreuses circonstances:
la source
Il existe cependant une bonne solution qui fonctionne dans (presque: voir commentaires) toutes les situations: une pompe à messages ad-hoc (SynchronizationContext).
Le thread appelant sera bloqué comme prévu, tout en garantissant que toutes les continuations appelées à partir de la fonction asynchrone ne se bloquent pas car elles seront marshalées vers le SynchronizationContext ad hoc (pompe de messages) exécuté sur le thread appelant.
Le code de l'assistant de pompe de message ad hoc:
Usage:
Une description plus détaillée de la pompe asynchrone est disponible ici .
la source
À quiconque prête attention à cette question ...
Si vous regardez,
Microsoft.VisualStudio.Services.WebApi
il y a une classe appeléeTaskExtensions
. Dans cette classe, vous verrez la méthode d'extension statiqueTask.SyncResult()
, qui comme bloque totalement le thread jusqu'au retour de la tâche.En interne, il appelle
task.GetAwaiter().GetResult()
ce qui est assez simple, mais il est surchargé pour fonctionner sur n'importe quelleasync
méthode qui retourneTask
,Task<T>
ouTask<HttpResponseMessage>
... du sucre syntaxique, bébé ... papa a le goût sucré.Il semble que
...GetAwaiter().GetResult()
c'est la manière officielle de MS d'exécuter du code asynchrone dans un contexte de blocage. Semble fonctionner très bien pour mon cas d'utilisation.la source
Ou utilisez ceci:
la source
Vous pouvez appeler n'importe quelle méthode asynchrone à partir du code synchrone, c'est-à-dire jusqu'à ce que vous en ayez besoin
await
, auquel cas elles doivent également être marquéesasync
.Comme beaucoup de gens le suggèrent ici, vous pouvez appeler Wait () ou Result sur la tâche résultante dans votre méthode synchrone, mais vous vous retrouvez avec un appel de blocage dans cette méthode, ce qui va à l'encontre du but de l'async.
Si vous ne pouvez vraiment pas créer votre méthode
async
et que vous ne voulez pas verrouiller la méthode synchrone, vous devrez utiliser une méthode de rappel en la passant en paramètre à la méthode ContinueWith sur la tâche.la source
async
aussi" a détourné mon attention de ce que vous disiez vraiment.Je sais que je suis tellement en retard. Mais au cas où quelqu'un comme moi voudrait résoudre ce problème d'une manière ordonnée, facile et sans dépendre d'une autre bibliothèque.
J'ai trouvé le morceau de code suivant de Ryan
alors vous pouvez l'appeler comme ça
la source
Après des heures à essayer différentes méthodes, avec plus ou moins de succès, c'est ce que j'ai fini avec. Il ne se termine pas dans une impasse tout en obtenant le résultat et il obtient et lève également l'exception d'origine et non celle encapsulée.
la source
Il pourrait être appelé depuis un nouveau thread (PAS depuis le pool de threads!):
la source
Ces méthodes asynchrones Windows ont une astucieuse petite méthode appelée AsTask (). Vous pouvez l'utiliser pour que la méthode se retourne en tant que tâche afin que vous puissiez appeler manuellement Wait () dessus.
Par exemple, sur une application Silverlight Windows Phone 8, vous pouvez effectuer les opérations suivantes:
J'espère que cela t'aides!
la source
Si vous voulez l'exécuter Sync
la source
RunSynchronously()
une tâche à chaud résulte en unInvalidOperationException
. Essayez-le avec ce code:Task.Run(() => {}).RunSynchronously();
la source