J'apprends sur async / wait et j'ai rencontré une situation où je dois appeler une méthode async de manière synchrone. Comment puis je faire ça?
Méthode asynchrone:
public async Task<Customers> GetCustomers()
{
return await Service.GetCustomersAsync();
}
Utilisation normale:
public async void GetCustomers()
{
customerList = await GetCustomers();
}
J'ai essayé d'utiliser les éléments suivants:
Task<Customer> task = GetCustomers();
task.Wait()
Task<Customer> task = GetCustomers();
task.RunSynchronously();
Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)
J'ai également essayé une suggestion d' ici , mais elle ne fonctionne pas lorsque le répartiteur est suspendu.
public static void WaitWithPumping(this Task task)
{
if (task == null) throw new ArgumentNullException(“task”);
var nestedFrame = new DispatcherFrame();
task.ContinueWith(_ => nestedFrame.Continue = false);
Dispatcher.PushFrame(nestedFrame);
task.Wait();
}
Voici l'exception et la trace de pile de l'appel RunSynchronously
:
System.InvalidOperationException
Message : RunSynchronously ne peut pas être appelé sur une tâche non liée à un délégué.
InnerException : null
Source : mscorlib
StackTrace :
at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
at System.Threading.Tasks.Task.RunSynchronously()
at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
at System.Collections.Generic.List`1.ForEach(Action`1 action)
at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at System.Threading.ExecutionContext.runTryCode(Object userData)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
c#
asynchronous
c#-5.0
async-await
Rachel
la source
la source
Task
synchrone; 3) J'intègre GeneXus avec le pilote MongoDB C # , qui expose certaines méthodes uniquement de manière asynchroneSemaphoreSlim
.Réponses:
Voici une solution de contournement que j'ai trouvée qui fonctionne pour tous les cas (y compris les répartiteurs suspendus). Ce n'est pas mon code et je travaille toujours pour bien le comprendre, mais ça marche.
Il peut être appelé en utilisant:
customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());
Le code est d' ici
la source
DynamicNodeProviderBase
classe de base, on ne peut pas le déclarer commeasync
méthode. Soit je devais remplacer par une nouvelle bibliothèque, soit simplement appeler un op synchrone.AspNetSynchronizationContext
, donc ce hack particulier ne fonctionnera pas si vous appelez ces API.Sachez que cette réponse a trois ans. Je l'ai écrit principalement basé sur une expérience avec .Net 4.0, et très peu avec 4.5 en particulier avec
async-await
. D'une manière générale, c'est une belle solution simple, mais cela casse parfois les choses. Veuillez lire la discussion dans les commentaires..Net 4.5
Utilisez simplement ceci:
Voir: TaskAwaiter , Task.Result , Task.RunSynchronously
.Net 4.0
Utilisez ceci:
...ou ca:
la source
.Result
peut produire une impasse dans certains scénariosResult
peut facilement provoquer un blocage dans leasync
code , comme je le décris sur mon blog.Surpris, personne n'a mentionné cela:
Pas aussi joli que certaines des autres méthodes ici, mais il présente les avantages suivants:
Wait
)AggregateException
(commeResult
)Task
etTask<T>
( essayez-le vous-même! )De plus, comme il
GetAwaiter
est de type canard, cela devrait fonctionner pour tout objet renvoyé par une méthode asynchrone (commeConfiguredAwaitable
ouYieldAwaitable
), pas seulement pour les tâches.edit: Veuillez noter qu'il est possible que cette approche (ou utilisation
.Result
) se bloque, sauf si vous vous assurez d'ajouter à.ConfigureAwait(false)
chaque fois que vous attendez, pour toutes les méthodes asynchrones qui peuvent éventuellement être atteintes à partir deBlahAsync()
(pas seulement celles qu'elle appelle directement). Explication .Si vous êtes trop paresseux pour ajouter
.ConfigureAwait(false)
partout et que vous ne vous souciez pas des performances, vous pouvez également le fairela source
GetAwaiter()
: "Cette méthode est destinée à l'utilisateur du compilateur plutôt qu'à utiliser directement dans le code."Il est beaucoup plus simple d'exécuter la tâche sur le pool de threads, plutôt que d'essayer de tromper le planificateur pour l'exécuter de manière synchrone. De cette façon, vous pouvez être sûr qu'il ne se bloquera pas. Les performances sont affectées en raison du changement de contexte.
la source
Task.Run(DoSomethingAsync)
place? Cela supprime un niveau de délégués.Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());
est plus explicite et répond au souci de @sgnsajgon qu'il pourrait renvoyer une tâche <Task <MyResult>>. La surcharge correcte de Task.Run est sélectionnée dans les deux cas, mais le délégué asynchrone rend votre intention évidente.La meilleure réponse est que non , les détails dépendant de la "situation".
Est-ce un getter / setter de propriété? Dans la plupart des cas, il vaut mieux avoir des méthodes asynchrones que des "propriétés asynchrones". (Pour plus d'informations, consultez mon article de blog sur les propriétés asynchrones ).
S'agit-il d'une application MVVM et vous souhaitez effectuer une liaison de données asynchrone? Utilisez ensuite quelque chose comme le mien
NotifyTask
, comme décrit dans mon article MSDN sur la liaison de données asynchrone .Est-ce un constructeur? Ensuite, vous voudrez probablement envisager une méthode d'usine asynchrone. (Pour plus d'informations, consultez mon article de blog sur les constructeurs asynchrones ).
Il y a presque toujours une meilleure réponse que de faire une synchronisation sur async.
Si ce n'est pas possible pour votre situation (et vous le savez en posant ici une question décrivant la situation ), je vous recommanderais simplement d'utiliser du code synchrone. Il est préférable d’asynchroniser complètement; la synchronisation complète est la deuxième meilleure option. La synchronisation sur async n'est pas recommandée.
Cependant, il existe une poignée de situations où la synchronisation sur async est nécessaire. Plus précisément, vous êtes contraint par le code appelant de sorte que vous devez être synchronisé (et que vous n'avez absolument aucun moyen de repenser ou de restructurer votre code pour autoriser l'asynchronie), et vous devez appeler le code asynchrone. C'est une situation très rare, mais elle revient de temps en temps.
Dans ce cas, vous devrez utiliser l'un des hacks décrits dans mon article sur le développement des friches industrielles
async
, en particulier:GetAwaiter().GetResult()
). Notez que cela peut provoquer des blocages (comme je le décris sur mon blog).Task.Run(..).GetAwaiter().GetResult()
). Notez que cela ne fonctionnera que si le code asynchrone peut être exécuté sur un thread de pool de threads (c'est-à-dire qu'il ne dépend pas d'une interface utilisateur ou d'un contexte ASP.NET).Les boucles de messages imbriquées sont les plus dangereuses de tous les hacks, car elles provoquent une nouvelle entrée . La ré-entrée est extrêmement difficile à raisonner et (IMO) est la cause de la plupart des bogues d'application sous Windows. En particulier, si vous êtes sur le thread d'interface utilisateur et que vous bloquez sur une file d'attente de travail (en attendant que le travail asynchrone se termine), le CLR effectue en fait un pompage de messages pour vous - il va réellement gérer certains messages Win32 à partir de votre code . Oh, et vous n'avez aucune idée de quels messages - quand Chris Brumme dit "Ce ne serait pas génial de savoir exactement ce qui sera pompé? Malheureusement, le pompage est un art noir qui est au-delà de la compréhension mortelle." , alors nous n'avons vraiment aucun espoir de savoir.
Donc, lorsque vous bloquez comme ça sur un thread d'interface utilisateur, vous demandez des ennuis. Une autre cbrumme cite du même article: "De temps en temps, les clients à l'intérieur ou à l'extérieur de l'entreprise découvrent que nous pompons des messages lors du blocage géré sur un STA [thread d'interface utilisateur]. C'est une préoccupation légitime, car ils savent que c'est très difficile pour écrire du code robuste face à la réentrance. "
Oui, ça l'est. Très difficile à écrire du code robuste face à la réentrance. Et les boucles de messages imbriquées vous obligent à écrire du code robuste face à la réentrance. C'est pourquoi la réponse acceptée (et la plus votée) à cette question est extrêmement dangereuse dans la pratique.
Si vous êtes complètement hors de toutes les autres options - vous ne pouvez pas reconcevoir votre code, vous ne pouvez pas le restructurer pour qu'il soit asynchrone - vous êtes obligé par le code d'appel immuable d'être synchronisé - vous ne pouvez pas changer le code en aval pour qu'il soit synchronisé - vous ne pouvez pas bloquer - vous ne pouvez pas exécuter le code asynchrone sur un thread séparé - alors et seulement alors si vous envisagez d'adopter la réentrance.
Si vous vous trouvez dans ce coin, je recommanderais d'utiliser quelque chose comme
Dispatcher.PushFrame
pour les applications WPF , en boucle avecApplication.DoEvents
pour les applications WinForm et pour le cas général, le mienAsyncContext.Run
.la source
Main()
méthode async ne compile pas; à un moment donné que vous avez obtenu à combler le fossé entre les mondes de synchronisation et async. Ce n'est pas une " situation très rare" , c'est nécessaire dans littéralement tous les programmes qui appellent une méthode asynchrone. Il n'y a pas d'option pour ne pas "faire de synchronisation sur async" , juste une option pour shunter ce fardeau jusqu'à la méthode appelante au lieu de l'assumer dans celle que vous écrivez actuellement.async
toutes les méthodes dans mon application maintenant. Et c'est beaucoup. Cela ne peut-il pas simplement être la valeur par défaut?Si je lis bien votre question - le code qui veut l'appel synchrone à une méthode asynchrone s'exécute sur un thread de répartiteur suspendu. Et vous voulez réellement bloquer de façon synchrone ce thread jusqu'à ce que la méthode asynchrone soit terminée.
Les méthodes asynchrones en C # 5 sont
Task
optimisées en découpant efficacement la méthode en morceaux sous le capot et en renvoyant une méthode qui peut suivre l'achèvement global de l'ensemble du shabang. Cependant, la façon dont les méthodes hachées s'exécutent peut dépendre du type de l'expression transmise à l'await
opérateur.La plupart du temps, vous utiliserez
await
une expression de typeTask
. L'implémentation duawait
modèle par Task est "intelligente" en ce sens qu'elle diffère de laSynchronizationContext
, ce qui provoque essentiellement les événements suivants:await
trouve sur un thread de boucle de message Dispatcher ou WinForms, il garantit que les segments de la méthode async se produisent dans le cadre du traitement de la file d'attente de messages.await
est sur un thread de pool de threads, les segments restants de la méthode async se produisent n'importe où sur le pool de threads.C'est pourquoi vous rencontrez probablement des problèmes - l'implémentation de la méthode async essaie d'exécuter le reste sur le Dispatcher - même si elle est suspendue.
.... sauvegarde! ....
Je dois poser la question, pourquoi essayez-vous de bloquer de manière synchrone sur une méthode asynchrone? Cela irait à l'encontre de l'objectif pour lequel la méthode voulait être appelée de manière asynchrone. En général, lorsque vous commencez à utiliser
await
une méthode Dispatcher ou UI, vous souhaiterez activer la synchronisation asynchrone de l'ensemble de votre flux d'interface utilisateur. Par exemple, si votre pile d'appels était quelque chose comme ceci:WebRequest.GetResponse()
YourCode.HelperMethod()
YourCode.AnotherMethod()
YourCode.EventHandlerMethod()
[UI Code].Plumbing()
-WPF
ouWinForms
CodeWPF
ouWinForms
Message LoopEnsuite, une fois que le code a été transformé pour utiliser async, vous vous retrouverez généralement avec
WebRequest.GetResponseAsync()
YourCode.HelperMethodAsync()
YourCode.AnotherMethodAsync()
YourCode.EventHandlerMethodAsync()
[UI Code].Plumbing()
-WPF
ouWinForms
CodeWPF
ouWinForms
Message LoopRépondre réellement
La classe AsyncHelpers ci-dessus fonctionne réellement car elle se comporte comme une boucle de messages imbriquée, mais elle installe sa propre mécanique parallèle sur le Dispatcher plutôt que d'essayer de s'exécuter sur le Dispatcher lui-même. C'est une solution de contournement pour votre problème.
Une autre solution consiste à exécuter votre méthode asynchrone sur un thread threadpool, puis à attendre qu'elle se termine. Il est facile de le faire - vous pouvez le faire avec l'extrait de code suivant:
L'API finale sera Task.Run (...), mais avec le CTP, vous aurez besoin des suffixes Ex ( explication ici ).
la source
TaskEx.RunEx(GetCustomers).Result
bloque l'application lorsqu'elle est exécutée sur un thread de répartiteur suspendu. En outre, la méthode GetCustomers () est normalement exécutée en mode asynchrone, mais dans une situation, elle doit s'exécuter de manière synchrone, donc je cherchais un moyen de le faire sans créer de version de synchronisation de la méthode.async
méthodes; les boucles imbriquées doivent certainement être évitées.Cela fonctionne bien pour moi
la source
MyAsyncMethod().RunTaskSynchronously();
La manière la plus simple que j'ai trouvée pour exécuter la tâche de manière synchrone et sans bloquer le thread d'interface utilisateur est d'utiliser RunSynchronously () comme:
Dans mon cas, j'ai un événement qui se déclenche lorsque quelque chose se produit. Je ne sais pas combien de fois cela se produira. Donc, j'utilise le code ci-dessus dans mon événement, donc chaque fois qu'il se déclenche, il crée une tâche. Les tâches sont exécutées de manière synchrone et cela fonctionne très bien pour moi. J'étais juste surpris qu'il m'ait fallu si longtemps pour découvrir cela, vu à quel point c'est simple. Habituellement, les recommandations sont beaucoup plus complexes et sujettes aux erreurs. C'était simple et propre.
la source
Je l'ai rencontré à plusieurs reprises, principalement lors de tests unitaires ou lors du développement d'un service Windows. Actuellement, j'utilise toujours cette fonctionnalité:
C'est simple, facile et je n'ai eu aucun problème.
la source
J'ai trouvé ce code dans le composant Microsoft.AspNet.Identity.Core, et cela fonctionne.
la source
Juste une petite note - cette approche:
fonctionne pour WinRT.
Laisse-moi expliquer:
De plus, cette approche ne fonctionne que pour les solutions du Windows Store!
Remarque: Cette méthode n'est pas sécurisée pour les threads si vous appelez votre méthode à l'intérieur d'une autre méthode asynchrone (selon les commentaires de @Servy)
la source
CancellationToken
ma solution.Dans votre code, votre première attente pour l'exécution de la tâche, mais vous ne l'avez pas démarrée, elle attend donc indéfiniment. Essaye ça:
Éditer:
Vous dites que vous obtenez une exception. Veuillez poster plus de détails, y compris la trace de la pile.
Mono contient le cas de test suivant:
Vérifiez si cela fonctionne pour vous. Si ce n'est pas le cas, bien que très peu probable, vous pourriez avoir une construction étrange d'Async CTP. Si cela fonctionne, vous souhaiterez peut-être examiner ce que le compilateur génère exactement et en quoi l'
Task
instanciation est différente de cet exemple.Édition n ° 2:
J'ai vérifié avec Reflector que l'exception que vous avez décrite se produit quand
m_action
estnull
. C'est un peu étrange, mais je ne suis pas un expert en CTP Async. Comme je l'ai dit, vous devez décompiler votre code et voir commentTask
est instancié exactement comment cela sem_action
faitnull
.PS Quel est le problème avec les downvotes occasionnels? Envie d'élaborer?
la source
RunSynchronously may not be called on a task unbound to a delegate
. Google n'est d'aucune aide puisque tous les résultats pour cela sont en chinois ...await
mot clé est utilisé. L'exception publiée dans mon commentaire précédent est l'exception que je reçois, bien que ce soit l'un des rares pour lesquels je ne puisse pas rechercher de cause ou de résolution.async
et lesasync
mots clés ne sont rien d'autre que du sucre de syntaxe. Compilateur génère du code pour créerTask<Customer>
dansGetCustomers()
c'est là que je regarderais d' abord. En ce qui concerne l'exception, vous avez uniquement publié un message d'exception, ce qui est inutile sans type d'exception et trace de pile. Appelez laToString()
méthode d'exception et publiez la sortie dans la question.Testé dans .Net 4.6. Il peut également éviter l'impasse.
Pour le retour de la méthode async
Task
.Pour le retour de la méthode async
Task<T>
Modifier :
Si l'appelant est en cours d'exécution dans le thread de pool de threads (ou l'appelant est également dans une tâche), il peut toujours provoquer un blocage dans certaines situations.
la source
Result
est parfait pour le travail si vous voulez un appel synchrone, et carrément dangereux sinon. Il n'y a rien dans le nomResult
ou dans l'intellisenseResult
qui indique qu'il s'agit d'un appel bloquant. Il devrait vraiment être renommé.utiliser le code ci-dessous
la source
Pourquoi ne pas créer un appel comme:
ce n'est pas asynchrone.
la source
Cette réponse est conçue pour tous ceux qui utilisent WPF pour .NET 4.5.
Si vous essayez de l'exécuter
Task.Run()
sur le thread GUI, puistask.Wait()
se bloque indéfiniment, si vous n'avez pas leasync
mot - clé dans votre définition de fonction.Cette méthode d'extension résout le problème en vérifiant si nous sommes sur le thread GUI, et si c'est le cas, en exécutant la tâche sur le thread de répartiteur WPF.
Cette classe peut servir de lien entre le monde asynchrone / attente et le monde non asynchrone / attente, dans les situations où cela est inévitable, comme les propriétés MVVM ou les dépendances avec d'autres API qui n'utilisent pas async / wait.
la source
Un simple appel
.Result;
ou.Wait()
un risque de blocage comme beaucoup l'ont dit dans les commentaires. Comme la plupart d'entre nous aiment les oneliners, vous pouvez les utiliser pour.Net 4.5<
Acquisition d'une valeur via une méthode asynchrone:
Appel synchrone d'une méthode asynchrone
Aucun problème de blocage ne se produira en raison de l'utilisation de
Task.Run
.La source:
https://stackoverflow.com/a/32429753/3850405
la source
Je pense que la méthode d'assistance suivante pourrait également résoudre le problème.
Peut être utilisé de la manière suivante:
la source
Cela fonctionne pour moi
la source
J'ai trouvé que SpinWait fonctionne assez bien pour cela.
L'approche ci-dessus n'a pas besoin d'utiliser .Result ou .Wait (). Il vous permet également de spécifier un délai d'expiration afin que vous ne soyez pas bloqué pour toujours au cas où la tâche ne se terminerait jamais.
la source
Sur wp8:
Emballe-le:
Appeler:
la source
la source
Ou vous pouvez simplement aller avec:
Pour cela, assurez-vous de référencer l'assembly d'extension:
la source
Essayez le code suivant, cela fonctionne pour moi:
la source