Pregunta ¿Cómo ejecutaría un método Async Task sincrónicamente?


Estoy aprendiendo acerca de async / await, y me encontré con una situación en la que necesito llamar a un método asíncrono sincrónicamente. ¿Cómo puedo hacer eso?

Método Async:

public async Task<Customers> GetCustomers()
{
    return await Service.GetCustomersAsync();
}

Uso normal:

public async void GetCustomers()
{
    customerList = await GetCustomers();
}

He intentado usar lo siguiente:

Task<Customer> task = GetCustomers();
task.Wait()

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)

También probé una sugerencia de aquíSin embargo, no funciona cuando el despachador está suspendido.

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

Aquí está la excepción y el seguimiento de la pila de llamadas RunSynchronously:

System.InvalidOperationException

Mensaje: RunSynchronously no se puede invocar en una tarea unbound a un delegado.

InnerException: nulo

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

504
2018-02-23 18:18


origen


Respuestas:


Aquí hay una solución alternativa que encontré que funciona para todos los casos (incluidos los despachadores suspendidos). No es mi código y todavía estoy trabajando para entenderlo completamente, pero funciona.

Se puede llamar usando:

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

El código es de aquí

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



Ser aconsejado esta respuesta tiene tres años. Lo escribí basado principalmente en una experiencia con .Net 4.0, y muy poco con 4.5 especialmente con async-await. En términos generales, es una solución sencilla y agradable, pero a veces rompe las cosas. Por favor lea la discusión en los comentarios.

.Net 4.5

Solo usa esto:

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

Ver: TaskAwaiter, Task.Result, Task.RunSynchronously


.Net 4.0

Utilizar esta:

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

x.AsyncWaitHandle.WaitOne();

...o esto:

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

281
2017-08-02 17:03



Sorprendido, nadie mencionó esto:

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

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

No es tan bonito como algunos de los otros métodos aquí, pero tiene los siguientes beneficios:

  • no traga excepciones (como Wait)
  • no envolverá ninguna excepción lanzada en un AggregateException (me gusta Result)
  • funciona para ambos Task y Task<T> (¡pruébalo tú mismo!)

Además, desde GetAwaiter está escrito en pato, esto debería funcionar para cualquier objeto que sea devuelto por un método asíncrono (como ConfiguredAwaitable o YieldAwaitable), no solo Tareas.


editar: Tenga en cuenta que es posible para este enfoque (o el uso .Result) a un punto muerto, a menos que se asegure de agregar .ConfigureAwait(false) cada vez que lo esperes, para todos los métodos de sincronización que posiblemente se puedan alcanzar desde BlahAsync() (no solo a los que llama directamente). Explicación.

// 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 eres demasiado perezoso para agregar .ConfigureAwait(false) en todas partes, y no te importa el rendimiento, puedes hacerlo alternativamente

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

74
2018-02-07 19:29



Es mucho más simple ejecutar la tarea en el grupo de subprocesos, en lugar de intentar engañar al programador para que lo ejecute de forma síncrona. De esta forma, puedes estar seguro de que no se estancará. El rendimiento se ve afectado por el cambio de contexto.

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



Estoy aprendiendo acerca de async / await, y me encontré con una situación en la que necesito llamar a un método asíncrono sincrónicamente. ¿Cómo puedo hacer eso?

La mejor respuesta es tu no, con los detalles dependiendo de lo que es la "situación".

¿Es un getter / setter de la propiedad? En la mayoría de los casos, es mejor tener métodos asíncronos que "propiedades asíncronas". (Para obtener más información, consulte mi blog post sobre propiedades asincrónicas)

¿Es esta una aplicación MVVM y quieres hacer un enlace de datos asíncrono? Entonces usa algo como mi NotifyTaskcomo se describe en mi Artículo de MSDN sobre enlace de datos asíncrono.

¿Es un constructor? Entonces probablemente desee considerar un método de fábrica asincrónico. (Para más información, mira mi publicación de blog sobre constructores asíncronos)

Casi siempre hay una mejor respuesta que hacer sincronización sincronizada.

Si no es posible para su situación (y lo sabe haciendo una pregunta aquí describiendo la situación), entonces recomendaría simplemente usar código síncrono. Async todo el camino es mejor; sincronizar todo el camino es el segundo mejor. Sync-over-async no es recomendable.

Sin embargo, hay un puñado de situaciones en las que es necesaria la sincronización por sincronización. Específicamente, está restringido por el código de llamada para que tener para ser sincronizado (y no tener absolutamente ninguna manera de volver a pensar o reestructurar su código para permitir la asincronía), y tú tener para llamar al código asíncrono. Esto es un muy situación rara, pero aparece de vez en cuando.

En ese caso, necesitarías usar uno de los hacks descritos en mi artículo sobre brownfield async desarrollo, específicamente:

  • Bloqueo (por ejemplo, GetAwaiter().GetResult()) Tenga en cuenta que esto puede causar puntos muertos (como lo describo en mi blog).
  • Ejecutar el código en un hilo del grupo de subprocesos (p. Task.Run(..).GetAwaiter().GetResult()) Tenga en cuenta que esto solo funcionará si el código asíncrono se puede ejecutar en un subproceso de grupo de subprocesos (es decir, no depende de un contexto de UI o ASP.NET).
  • Bucles de mensajes anidados. Tenga en cuenta que esto solo funcionará si el código asíncrono solo asume un contexto de subproceso único, no un específico tipo de contexto (una gran cantidad de códigos de UI y ASP.NET esperan un contexto específico).

Los bucles de mensajes anidados son los más peligrosos de todos los hacks, porque causan reentrada. La reentrada es extremadamente difícil de razonar, y (IMO) es la causa de la mayoría de los errores de aplicación en Windows. En particular, si está en el hilo de la interfaz de usuario y bloquea en una cola de trabajo (esperando a que se complete el trabajo de sincronización), entonces la CLR en realidad hace un mensaje de bombeo para usted - realmente manejará algunos mensajes de Win32 desde dentro de tu código. Ah, y no tienes idea de qué mensajes, cuándo Chris Brumme  dice "¿No sería genial saber exactamente qué se bombeará? Desafortunadamente, el bombeo es un arte negro que está más allá de la comprensión mortal"., entonces realmente no tenemos esperanza de saber.

Entonces, cuando bloqueas así en un hilo de interfaz de usuario, estás buscando problemas. Otra cita del mismo artículo: "De vez en cuando, los clientes dentro o fuera de la compañía descubren que estamos transfiriendo mensajes durante el bloqueo administrado en un STA [UI thread]. Esta es una preocupación legítima, porque saben que es muy difícil para escribir código que sea robusto frente a la reentrada ".

Sí lo es. Muy código difícil de escribir que es robusto frente a la reentrada. Y bucles de mensajes anidados fuerzaa escribir código que sea robusto frente a la reentrada. Esta es la razón por la respuesta aceptada (y la más votada) para esta pregunta es extremadamente peligroso en la práctica.

Si está completamente fuera de todas las demás opciones, no puede rediseñar su código, no puede reestructurarlo para que sea asíncrono, el código de llamada inmutable le obliga a sincronizar, no puede cambiar el código de flujo descendente para sincronizarlo - no puedes bloquear - no puedes ejecutar el código asíncrono en un hilo separado - entonces y solo entonces ¿Debería considerar abrazar la reentrada?

Si te encuentras en esta esquina, te recomendaría usar algo como Dispatcher.PushFrame para aplicaciones WPF, haciendo bucles con Application.DoEvents para aplicaciones WinForm, y para el caso general, mi propio AsyncContext.Run.


35
2017-09-07 13:40



Esto está funcionando bien para mí

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 estoy leyendo correctamente su pregunta, el código que desea la llamada síncrona a un método asíncrono se está ejecutando en una cadena suspendida suspendida. Y quieres sincronizar realmente bloquear ese hilo hasta que se complete el método asíncrono.

Los métodos asíncronos en C # 5 se alimentan cortando eficazmente el método en piezas bajo el capó, y devolviendo un Task que puede rastrear la finalización total de todo el shabang. Sin embargo, cómo se ejecutan los métodos fragmentados puede depender del tipo de expresión pasada al await operador.

La mayoría de las veces, estarás usando await en una expresión de tipo Task. La implementación de la tarea del await patrón es "inteligente" en el sentido de que difiere de la SynchronizationContext, que básicamente hace que ocurra lo siguiente:

  1. Si el hilo entra al await está en un hilo de bucle de mensaje Dispatcher o WinForms, asegura que los fragmentos del método async ocurren como parte del procesamiento de la cola de mensajes.
  2. Si el hilo entra al await está en un hilo del grupo de subprocesos, entonces los fragmentos restantes del método asíncrono ocurren en cualquier parte del grupo de subprocesos.

Es por eso que probablemente tenga problemas: la implementación del método asincrónico está intentando ejecutar el resto en el Dispatcher, aunque esté suspendido.

.... retrocediendo! ....

Tengo que hacer la pregunta, por qué ¿Estás tratando de bloquear sincrónicamente un método asíncrono? Si lo hace, vencerá el propósito de por qué el método quería ser llamado asincrónicamente. En general, cuando empiezas a usar await en un método Dispatcher o UI, querrá convertir el flujo de UI completo en asincrónico. Por ejemplo, si su pila de llamadas fue algo como lo siguiente:

  1. [Arriba] WebRequest.GetResponse ()
  2. YourCode.HelperMethod ()
  3. YourCode.AnotherMethod ()
  4. YourCode.EventHandlerMethod ()
  5. [UI Code] .Plumbing () - Código WPF o WinForms
  6. [Message Loop] - WPF o WinForms Message Loop

Luego, una vez que el código se ha transformado para usar asincrónico, generalmente terminará con

  1. [Arriba] WebRequest.GetResponseAsync ()
  2. YourCode.HelperMethodAsync ()
  3. YourCode.AnotherMethodAsync ()
  4. YourCode.EventHandlerMethodAsync ()
  5. [UI Code] .Plumbing () - Código WPF o WinForms
  6. [Message Loop] - WPF o WinForms Message Loop

En realidad respondiendo

La clase AsyncHelpers anterior en realidad funciona porque se comporta como un bucle de mensajes anidados, pero instala su propia mecánica paralela al Dispatcher en lugar de intentar ejecutar en el propio Dispatcher. Esa es una solución para su problema.

Otra solución consiste en ejecutar su método async en una secuencia de subprocesos y luego esperar a que se complete. Hacerlo es fácil: puedes hacerlo con el siguiente fragmento:

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

La API final será Task.Run (...), pero con CTP necesitarás los sufijos Ex (explicación aquí)


18
2018-02-24 21:29