¿Cómo ejecutaría un método Task<T> asíncrono de forma sincrónica?

Resuelto Rachel asked hace 13 años • 22 respuestas

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

Método asíncrono:

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

Uso normal:

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

Intenté 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 desde aquí , sin embargo, no funciona cuando el despachador está en estado 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 la llamada RunSynchronously:

System.InvalidOperationException

Mensaje : No se puede invocar RunSynchronfully en una tarea no vinculada a un delegado.

Excepción interna : nula

Fuente : mscorlib

Seguimiento de pila :

          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()
Rachel avatar Feb 24 '11 01:02 Rachel
Aceptado

He aquí una solución alternativa que encontré y que funciona en 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>
    /// Synchronously execute's an async Task method which has a void return value.
    /// </summary>
    /// <param name="task">The Task method to execute.</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var syncContext = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(syncContext);
        
        syncContext.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                syncContext.InnerException = e;
                throw;
            }
            finally
            {
                syncContext.EndMessageLoop();
            }
        }, null);
        
        syncContext.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

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

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

        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;
        }
    }
}
Rachel avatar Feb 23 '2011 21:02 Rachel

Tenga en cuenta que esta respuesta tiene tres años. Lo escribí basándome 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 estropea algunas cosas. Lea la discusión en los comentarios.

.Neto 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.RunSynchronfully


.Net 4.0

Utilizar esta:

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

x.AsyncWaitHandle.WaitOne();

...o esto:

task.Start();
task.Wait();
AK_ avatar Aug 02 '2012 17:08 AK_

Me sorprende que nadie haya mencionado 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 acepta excepciones (como Wait)
  • no incluirá ninguna excepción lanzada en un AggregateException(me gusta Result)
  • funciona para ambos Tasky Task<T>(¡ pruébelo usted mismo! )

Además, dado que GetAwaiteres de tipo pato, esto debería funcionar para cualquier objeto que se devuelva desde un método asíncrono (como ConfiguredAwaitableo YieldAwaitable), no solo para Tareas.


editar: tenga en cuenta que es posible que este enfoque (o el uso de .Result) se bloquee, a menos que se asegure de agregar .ConfigureAwait(false)cada vez que espere, para todos los métodos asíncronos a los que se pueda acceder BlahAsync()(no solo 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 vago para agregar .ConfigureAwait(false)en todas partes y no te importa el rendimiento, puedes hacerlo alternativamente

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()
James Ko avatar Feb 07 '2016 19:02 James Ko

Es mucho más sencillo ejecutar la tarea en el grupo de subprocesos, en lugar de intentar engañar al programador para que la ejecute de forma sincrónica. De esa manera puedes estar seguro de que no se bloqueará. El rendimiento se ve afectado debido al 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; 
Michael L Perry avatar Jun 13 '2013 18:06 Michael L Perry