¿Cómo ejecutaría un método Task<T> asíncrono de forma sincrónica?
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()
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;
}
}
}
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();
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 gustaResult
) - funciona para ambos
Task
yTask<T>
(¡ pruébelo usted mismo! )
Además, dado que GetAwaiter
es de tipo pato, esto debería funcionar para cualquier objeto que se devuelva desde un método asíncrono (como ConfiguredAwaitable
o 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()
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;