Question Comment exécuter une méthode asynchrone Task de manière synchrone?


J'apprends à propos d'async / await, et je suis tombé sur une situation où j'ai besoin d'appeler une méthode asynchrone de manière synchrone. Comment puis je faire ça?

Méthode Async:

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 aussi essayé une suggestion de iciCependant, cela ne fonctionne pas lorsque le répartiteur est en état de suspension.

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: nul

La source: mscorlib

Trace de la pile:

          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()

504
2018-02-23 18:18


origine


Réponses:


Voici une solution de contournement que j'ai trouvé 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 provient de ici

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}

373
2018-02-23 21:02



Soyez avisé cette réponse a trois ans. Je l'ai écrit basé principalement 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 solution simple, mais qui casse parfois les choses. Veuillez lire la discussion dans les commentaires.

.Net 4.5

Utilisez simplement ceci:

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

Voir: TaskAwaiter, Task.Result, Task.RunSynchronously


.Net 4.0

Utilisez ceci:

var x = (IAsyncResult)task;
task.Start();

x.AsyncWaitHandle.WaitOne();

...ou ca:

task.Start();
task.Wait();

281
2017-08-02 17:03



Surpris, personne n'a mentionné cela:

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

Pas aussi joli que certaines des autres méthodes ici, mais il a les avantages suivants:

  • il n'avale pas les exceptions (comme Wait)
  • il ne sera pas envelopper les exceptions jetées dans un AggregateException (comme Result)
  • travaille pour les deux Task et Task<T> (essayez-le vous-même!)

Aussi, depuis GetAwaiter est duck-typed, cela devrait fonctionner pour tout objet renvoyé par une méthode async (comme ConfiguredAwaitable ou YieldAwaitable), pas seulement des tâches.


modifier: S'il vous plaît noter qu'il est possible pour cette approche (ou en utilisant .Result) à l'impasse, à moins que vous vous assuriez d'ajouter .ConfigureAwait(false) chaque fois que vous attendez, pour toutes les méthodes asynchrones qui peuvent éventuellement être atteintes BlahAsync() (pas seulement ceux qu'il appelle directement). Explication.

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

Si vous êtes trop paresseux pour ajouter .ConfigureAwait(false) partout, et vous ne vous souciez pas de la performance que vous pouvez alternativement faire

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()

74
2018-02-07 19:29



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 qu'il s'exécute 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.

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 

65
2018-06-13 18:50



J'apprends à propos d'async / await, et je suis tombé sur une situation où j'ai besoin d'appeler une méthode asynchrone de manière synchrone. Comment puis je faire ça?

La meilleure réponse est tu n'as pas, avec 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, voir mon article de blog sur les propriétés asynchrones).

Est-ce une application MVVM et vous voulez faire une liaison de données asynchrone? Ensuite, utilisez quelque chose comme mon 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, voir mon article de blog sur les constructeurs asynchrones).

Il y a presque toujours une meilleure réponse que de faire de la synchronisation sur asynchrone.

Si ce n'est pas possible pour votre situation (et vous le savez en posant une question ici décrivant la situation), alors je vous recommande d’utiliser simplement du code synchrone. Async tout le chemin est le meilleur; synchroniser tout le chemin est le deuxième meilleur. Sync-over-async n'est pas recommandé.

Cependant, il existe une poignée de situations où la synchronisation sur asynchrone est nécessaire. Plus précisément, vous êtes contraint par le code d'appel de sorte que vous avoir être synchrone (et n'avoir absolument aucun moyen de repenser ou de restructurer votre code pour permettre l'asynchronie), et toi avoir appeler le code asynchrone. C'est un très situation rare, mais cela arrive de temps en temps.

Dans ce cas, vous devrez utiliser l'un des hacks décrits dans mon article sur brownfield async développement, Plus précisément:

  • Blocage (par exemple, GetAwaiter().GetResult()). Notez que cela peut provoquer des blocages (comme je le décris sur mon blog).
  • Exécution du code sur un thread de pool de threads (par exemple, 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'un contexte d'interface utilisateur ou ASP.NET).
  • Boucles de message imbriquées. Notez que cela ne fonctionnera que si le code asynchrone suppose uniquement un contexte mono-thread, pas un spécifique type de contexte (beaucoup de code UI et ASP.NET attendent un contexte spécifique).

Les boucles de messages imbriquées sont les plus dangereuses de tous les hacks, car elles causent ré-entrée. La ré-entrée est extrêmement difficile à raisonner, et (OMI) est la cause de la plupart des bogues d'application sur Windows. En particulier, si vous êtes sur le thread de l'interface utilisateur et que vous bloquez dans une file d'attente de travail (en attendant que le travail asynchrone soit terminé), le CLR effectue un pompage de messages pour vous - il va gérer certains messages Win32 à partir de votre code. Oh, et vous n'avez aucune idée de quels messages - quand Chris Brumme  "Ce ne serait pas génial de savoir exactement ce qui va être 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.

Ainsi, lorsque vous bloquez comme cela sur un fil d'interface utilisateur, vous demandez des problèmes. Un autre commentaire du même article: "De temps en temps, des clients internes ou externes à l'entreprise découvrent que nous pompons des messages lors d'un blocage géré sur un thread STA [UI]. C'est une préoccupation légitime, car ils savent que c'est très difficile écrire du code robuste face à la réentrance. "

Oui, ça l'est. Très difficile d'écrire du code qui soit robuste face à la réentrance. Et des boucles de messages imbriquées Obligervous écrivez un code robuste face à la réentrance. C'est pourquoi la réponse acceptée (et la plus votée) pour cette question est extrêmement dangereux en pratique.

Si vous êtes complètement à court d'autres options - vous ne pouvez pas reconcevoir votre code, vous ne pouvez pas le restructurer pour qu'il soit asynchrone - vous êtes contraint par un code d'appel immuable à être synchronisé - vous ne pouvez pas changer le code aval à synchroniser - 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 avec Application.DoEvents pour les applications WinForm, et pour le cas général, le mien AsyncContext.Run.


35
2017-09-07 13:40



Cela fonctionne bien pour moi

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}

19
2017-07-23 05:42



Si je lis bien votre question - le code qui veut que l'appel synchrone à une méthode async est en cours d'exécution sur un thread de distributeur suspendu. Et vous voulez réellement synchroniquement bloc ce thread jusqu'à ce que la méthode asynchrone soit terminée.

Les méthodes asynchrones dans C # 5 sont alimentées en découpant efficacement la méthode en morceaux sous le capot, et en retournant un Task qui peut suivre l'achèvement global de l'ensemble shabang. Cependant, l'exécution des méthodes hachées peut dépendre du type de l'expression transmise au await opérateur.

La plupart du temps, vous utiliserez await sur une expression de type Task. La mise en œuvre de la tâche de la await motif est "intelligent" en ce qu'il se reporte à la SynchronizationContext, ce qui provoque essentiellement ce qui suit:

  1. Si le fil entrant dans le await est sur un thread de boucle de message Dispatcher ou WinForms, il s'assure que les morceaux de la méthode async se produisent dans le cadre du traitement de la file d'attente de messages.
  2. Si le fil entrant dans le await est sur un thread de pool de threads, puis les morceaux 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 asynchrone tente d'exécuter le reste sur le Dispatcher - même si elle est suspendue.

.... sauvegarder! ....

Je dois poser la question, Pourquoi essayez-vous de bloquer de manière synchrone sur une méthode asynchrone? Cela irait à l'encontre de la raison pour laquelle la méthode voulait être appelée de manière asynchrone. En général, lorsque vous commencez à utiliser await sur une méthode Dispatcher ou UI, vous devrez activer l'ensemble de votre flux d'interface utilisateur asynchrone. Par exemple, si votre callstack ressemblait à ceci:

  1. [Top] WebRequest.GetResponse ()
  2. YourCode.HelperMethod ()
  3. YourCode.AnotherMethod ()
  4. YourCode.EventHandlerMethod ()
  5. [Code UI] .Plumbing () - WPF ou WinForms Code
  6. [Boucle de message] - Boucle de message WPF ou WinForms

Ensuite, une fois que le code a été transformé pour utiliser l’async, vous allez généralement vous retrouver avec

  1. [Top] WebRequest.GetResponseAsync ()
  2. YourCode.HelperMethodAsync ()
  3. YourCode.AnotherMethodAsync ()
  4. YourCode.EventHandlerMethodAsync ()
  5. [Code UI] .Plumbing () - WPF ou WinForms Code
  6. [Boucle de message] - Boucle de message WPF ou WinForms

Répondre en fait

La classe AsyncHelpers ci-dessus fonctionne réellement car elle se comporte comme une boucle de message imbriquée, mais elle installe son propre mécanisme parallèle à Dispatcher plutôt que d'essayer d'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 async sur un thread Threadpool, puis attendez qu'elle se termine. Cela est facile - vous pouvez le faire avec l'extrait suivant:

var customerList = TaskEx.RunEx(GetCustomers).Result;

L'API finale sera Task.Run (...), mais avec le CTP vous aurez besoin des suffixes Ex (explication ici).


18
2018-02-24 21:29



Le moyen le plus simple que j'ai trouvé pour exécuter la tâche de manière synchrone et sans bloquer le thread d'interface utilisateur est d'utiliser RunSynchronously () comme:

Task t = new Task(() => 
{ 
   //.... YOUR CODE ....
});
t.RunSynchronously();

Dans mon cas, j'ai un événement qui se déclenche quand 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, cela 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'ai été surpris de constater que cela m'a pris tellement de temps pour en savoir plus à quel point c'est simple. Habituellement, les recommandations sont beaucoup plus complexes et sujettes aux erreurs. C'était simple et propre.


16
2017-10-25 23:22