Estoy aprendiendo sobre async / await, y me encontré con una situación en la que necesito llamar a un método asincrónico sincrónicamente. ¿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();
}
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 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 pila de la llamada RunSynchronously
:
System.InvalidOperationException
Mensaje : RunSynchronously no se puede invocar en una tarea desvinculada 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()
c#
asynchronous
c#-5.0
async-await
Rachel
fuente
fuente
Task
sincrónicamente; 3) Estoy integrando GeneXus con el controlador MongoDB C # , que expone algunos métodos solo de forma asíncronaSemaphoreSlim
.Respuestas:
Aquí hay una solución 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í
fuente
DynamicNodeProviderBase
clase base, no se puede declarar comoasync
método. O tuve que reemplazar con una nueva biblioteca, o simplemente llamar a una operación sincrónica.AspNetSynchronizationContext
, por lo que este truco en particular no funcionará si está llamando a esas API.Tenga en cuenta que 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 buena solución simple, pero a veces rompe cosas. Por favor lea la discusión en los comentarios..Net 4.5
Solo usa esto:
Ver: TaskAwaiter , Task.Result , Task.RunSynchronously
.Net 4.0
Utilizar este:
...o esto:
fuente
.Result
puede producir un punto muerto en ciertos escenariosResult
puede provocar un punto muerto en elasync
código , como lo describo en mi blog.Sorprendido, nadie mencionó esto:
No es tan bonito como algunos de los otros métodos aquí, pero tiene los siguientes beneficios:
Wait
)AggregateException
(comoResult
)Task
yTask<T>
(¡ pruébalo tú 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 (comoConfiguredAwaitable
oYieldAwaitable
), no solo Tareas.editar: Tenga en cuenta que es posible que este enfoque (o uso
.Result
) se interrumpa, a menos que se asegure de agregar.ConfigureAwait(false)
cada vez que espere, para todos los métodos asincrónicos a los que se puede accederBlahAsync()
(no solo los que llama directamente). Explicación .Si eres demasiado vago para agregar en
.ConfigureAwait(false)
todas partes, y no te importa el rendimiento, puedes hacerlo alternativamentefuente
GetAwaiter()
"Este método está destinado al usuario del compilador en lugar de usarse directamente en el código".Es mucho más simple 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 estancará. El rendimiento se ve afectado por el cambio de contexto.
fuente
Task.Run(DoSomethingAsync)
lugar? Esto elimina un nivel de delegados.Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());
es más explícito y aborda la preocupación de @sgnsajgon de que podría estar devolviendo una Tarea <Tarea <MiResultado>>. La sobrecarga correcta de Task.Run se selecciona de cualquier manera, pero el delegado asíncrono hace que su intención sea obvia.La mejor respuesta es que no , con los detalles que dependen de cuál es la "situación".
¿Es un captador / establecedor de propiedades? En la mayoría de los casos, es mejor tener métodos asincrónicos que "propiedades asincrónicas". (Para obtener más información, consulte la publicación de mi blog sobre propiedades asincrónicas ).
¿Es esta una aplicación MVVM y quieres hacer un enlace de datos asíncrono? Luego use algo como mi
NotifyTask
, como 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 asíncrono. (Para obtener más información, consulte la publicación de mi blog sobre constructores asincrónicos ).
Casi siempre hay una mejor respuesta que hacer sync-over-async.
Si no es posible para su situación (y lo sabe haciendo una pregunta aquí que describe la situación ), entonces recomendaría simplemente usar código síncrono. Asíncrono todo el camino es mejor; sincronizar todo el camino es el segundo mejor. Sync-over-async no se recomienda.
Sin embargo, hay algunas situaciones en las que es necesaria la sincronización sobre sincronización. En concreto, se ven limitados por el código de llamada para que tenga que ser de sincronización (y no tienen absolutamente ninguna manera de re-pensar o re-estructura de su código para permitir la asincronía), y que tienen que llamar asíncrono código. Esta es una situación muy rara, pero surge de vez en cuando.
En ese caso, necesitaría usar uno de los hacks descritos en mi artículo sobre desarrollo brownfield
async
, específicamente:GetAwaiter().GetResult()
. Ej .). Tenga en cuenta que esto puede causar puntos muertos (como lo describo en mi blog).Task.Run(..).GetAwaiter().GetResult()
). Tenga en cuenta que esto solo funcionará si el código asincrónico se puede ejecutar en un subproceso de grupo de subprocesos (es decir, no depende de una interfaz de usuario o contexto ASP.NET).Los bucles de mensajes anidados son los más peligrosos de todos los hacks, ya que provocan reencuentro . El reencuentro 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 subproceso de la interfaz de usuario y bloquea en una cola de trabajo (esperando que se complete el trabajo asincrónico), entonces el CLR realmente envía algunos mensajes por usted: en realidad manejará algunos mensajes Win32 desde su código . Ah, y no tienes idea de qué mensajes, cuando Chris Brumme dice "¿No sería genial saber exactamente qué se bombeará? Desafortunadamente, bombear es un arte negro que está más allá de la comprensión mortal". , entonces realmente no tenemos esperanza de saberlo.
Entonces, cuando bloqueas así en un hilo de la interfaz de usuario, estás pidiendo problemas. Otra cita de cbrumme del mismo artículo: "De vez en cuando, los clientes dentro o fuera de la empresa descubren que estamos enviando mensajes durante el bloqueo administrado en un STA [subproceso de interfaz de usuario]. 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. Código muy difícil de escribir que es robusto frente a la reentrada. Y los bucles de mensajes anidados lo obligan a escribir código que sea robusto frente a la reentrada. Esta es la razón por la cual la respuesta aceptada (y más votada) para esta pregunta es extremadamente peligrosa 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 lo obliga a sincronizarse; no puede cambiar el código descendente para que se sincronice - no puede bloquear - no puede ejecutar el código asíncrono en un hilo separado - entonces y solo entonces debería considerar aceptar la reentrada.
Si te encuentras en esta esquina, recomendaría usar algo como
Dispatcher.PushFrame
para aplicaciones WPF , bucleApplication.DoEvents
para aplicaciones WinForm, y para el caso general, el míoAsyncContext.Run
.fuente
Main()
método asíncrono no se compila; en algún momento usted ha conseguido cerrar la brecha entre los mundos síncronas y asíncronas. No es una " situación muy rara" , es necesaria en literalmente cada programa que llama a un método asíncrono. No hay una opción para no "sincronizar a través de la sincronización" , solo una opción para desviar esa carga al método de llamada en lugar de cargarla en la que está escribiendo actualmente.async
todos los métodos en mi aplicación ahora. Y eso es mucho. ¿No puede ser esto el valor predeterminado?Si estoy leyendo su pregunta correctamente, el código que desea la llamada síncrona a un método asíncrono se está ejecutando en un hilo de despachador suspendido. Y realmente desea bloquear ese hilo sincrónicamente hasta que se complete el método asíncrono.
Los métodos asíncronos en C # 5 funcionan al cortar eficazmente el método en pedazos debajo del capó y devolver uno
Task
que puede rastrear la finalización general de todo el shabang. Sin embargo, la forma en que se ejecutan los métodos cortados puede depender del tipo de expresión que se pasa alawait
operador.La mayoría de las veces, usará
await
una expresión de tipoTask
. La implementación delawait
patrón por parte de la tarea es "inteligente", ya que difiere de loSynchronizationContext
que básicamente hace que ocurra lo siguiente:await
está en un hilo de bucle de mensaje de Dispatcher o WinForms, asegura que los fragmentos del método asíncrono ocurran como parte del procesamiento de la cola de mensajes.await
está en un subproceso de grupo de subprocesos, los fragmentos restantes del método asíncrono se producen en cualquier parte del grupo de subprocesos.Es por eso que probablemente tengas problemas: la implementación del método asincrónico está tratando de ejecutar el resto en el Dispatcher, a pesar de que está suspendido.
.... retrocediendo! ....
Tengo que hacer la pregunta, ¿por qué? estás tratando de bloquear sincrónicamente un método asíncrono? Hacerlo sería contrario al propósito de por qué el método quería llamarse de forma asincrónica. En general, cuando comience a usar
await
un Dispatcher o un método de IU, querrá convertir todo su flujo de IU en asíncrono. Por ejemplo, si su pila de llamadas era algo como lo siguiente:WebRequest.GetResponse()
YourCode.HelperMethod()
YourCode.AnotherMethod()
YourCode.EventHandlerMethod()
[UI Code].Plumbing()
-WPF
oWinForms
códigoWPF
oWinForms
Bucle de mensajesLuego, una vez que el código se ha transformado para usar asíncrono, generalmente terminará con
WebRequest.GetResponseAsync()
YourCode.HelperMethodAsync()
YourCode.AnotherMethodAsync()
YourCode.EventHandlerMethodAsync()
[UI Code].Plumbing()
-WPF
oWinForms
códigoWPF
oWinForms
Bucle de mensajesEn realidad respondiendo
La clase AsyncHelpers anterior realmente funciona porque se comporta como un bucle de mensaje anidado, pero instala su propia mecánica paralela en el Dispatcher en lugar de intentar ejecutar en el Dispatcher. Esa es una solución para su problema.
Otra solución es ejecutar su método asíncrono en un hilo de grupo de subprocesos y luego esperar a que se complete. Hacerlo es fácil: puede hacerlo con el siguiente fragmento:
La API final será Task.Run (...), pero con el CTP necesitará los sufijos Ex ( explicación aquí ).
fuente
TaskEx.RunEx(GetCustomers).Result
bloquea la aplicación cuando se ejecuta en un hilo de despachador suspendido. Además, el método GetCustomers () normalmente se ejecuta de forma asíncrona, sin embargo, en una situación debe ejecutarse de forma sincrónica, por lo que estaba buscando una manera de hacerlo sin crear una versión de sincronización del método.async
métodos adecuadamente ; los bucles anidados ciertamente deben evitarse.Esto me esta funcionando bien
fuente
MyAsyncMethod().RunTaskSynchronously();
La forma más simple que he encontrado para ejecutar tareas sincrónicamente y sin bloquear el hilo de la interfaz de usuario es usar RunSynchronously () como:
En mi caso, tengo un evento que se dispara cuando ocurre algo. No sé cuántas veces ocurrirá. Entonces, utilizo el código anterior en mi evento, por lo que cada vez que se dispara, crea una tarea. Las tareas se ejecutan sincrónicamente y funciona muy bien para mí. Me sorprendió que me tomó tanto tiempo descubrir esto considerando lo simple que es. Por lo general, las recomendaciones son mucho más complejas y propensas a errores. Esto fue simple y limpio.
fuente
Lo he enfrentado varias veces, principalmente en pruebas unitarias o en el desarrollo de un servicio de Windows. Actualmente siempre uso esta función:
Es simple, fácil y no tuve problemas.
fuente
Encontré este código en el componente Microsoft.AspNet.Identity.Core, y funciona.
fuente
Solo una pequeña nota: este enfoque:
Funciona para WinRT.
Dejame explicar:
Además, este enfoque solo funciona para las soluciones de la Tienda Windows.
Nota: esta forma no es segura para subprocesos si llama a su método dentro de otro método asíncrono (según los comentarios de @Servy)
fuente
CancellationToken
mi solución.En su código, su primera espera para que se ejecute la tarea pero no la ha iniciado, por lo que espera indefinidamente. Prueba esto:
Editar:
Dices que tienes una excepción. Publique más detalles, incluido el seguimiento de la pila.
Mono contiene el siguiente caso de prueba:
Comprueba si esto funciona para ti. Si no es así, aunque es muy poco probable, es posible que tenga una versión extraña de Async CTP. Si funciona, es posible que desee examinar qué genera exactamente el compilador y cómo la
Task
creación de instancias es diferente de esta muestra.Editar # 2:
Verifiqué con Reflector que la excepción que describiste ocurre cuando
m_action
esnull
. Esto es un poco extraño, pero no soy un experto en Async CTP. Como dije, deberías descompilar tu código y ver cómoTask
se está instanciando exactamente cómom_action
esnull
.PD: ¿Cuál es el trato con los votos negativos ocasionales? ¿Cuidado para elaborar?
fuente
RunSynchronously may not be called on a task unbound to a delegate
. Google no es de ayuda ya que todos los resultados están en chino ...await
se usa la palabra clave. La excepción publicada en mi comentario anterior es la excepción que obtengo, aunque es una de las pocas que no puedo buscar en Google y encontrar una causa o resolución.async
y lasasync
palabras clave no son más que sintaxis de azúcar. Compilador genera código para crearTask<Customer>
en elGetCustomers()
modo que es donde me gustaría ver primero. En cuanto a la excepción, solo publicó un mensaje de excepción, que es inútil sin el tipo de excepción y el seguimiento de la pila. Llame alToString()
método de excepción y publique la salida en la pregunta.Probado en .Net 4.6. También puede evitar el punto muerto.
Para el método asíncrono que regresa
Task
.Para el método asíncrono que regresa
Task<T>
Editar :
Si la persona que llama se está ejecutando en el subproceso del grupo de subprocesos (o si también está en una tarea), aún puede causar un punto muerto en alguna situación.
fuente
Result
es perfecto para el trabajo si desea una llamada sincrónica y, de lo contrario, es totalmente peligroso. No hay nada en el nombreResult
o en la inteligenciaResult
que indique que es una llamada de bloqueo. Realmente debería ser renombrado.use el siguiente código
fuente
¿Por qué no crear una llamada como:
eso no es asíncrono.
fuente
Esta respuesta está diseñada para cualquier persona que use WPF para .NET 4.5.
Si intenta ejecutar
Task.Run()
en el hilo de la GUI,task.Wait()
se bloqueará indefinidamente, si no tiene laasync
palabra clave en la definición de su función.Este método de extensión resuelve el problema verificando si estamos en el hilo de la GUI y, de ser así, ejecutando la tarea en el hilo del despachador de WPF.
Esta clase puede actuar como el pegamento entre el mundo asíncrono / espera y el mundo no asíncrono / espera, en situaciones donde es inevitable, como las propiedades MVVM o dependencias de otras API que no usan asíncrono / espera.
fuente
Simplemente llamar
.Result;
o.Wait()
es un riesgo de puntos muertos como muchos han dicho en los comentarios. Como a la mayoría de nosotros nos gustan las líneas, puede usarlas para.Net 4.5<
Adquisición de un valor a través de un método asíncrono:
Llamar sincrónicamente a un método asíncrono
No se producirán problemas de punto muerto debido al uso de
Task.Run
.Fuente:
https://stackoverflow.com/a/32429753/3850405
fuente
Creo que el siguiente método auxiliar también podría resolver el problema.
Se puede usar de la siguiente manera:
fuente
Esto funciona para mi
fuente
He descubierto que SpinWait funciona bastante bien para esto.
El enfoque anterior no necesita usar .Result o .Wait (). También le permite especificar un tiempo de espera para que no se quede atascado para siempre en caso de que la tarea nunca se complete.
fuente
En wp8:
Envolverlo:
Llámalo:
fuente
fuente
O simplemente podrías ir con:
Para que esto se compile, asegúrese de hacer referencia al ensamblaje de extensión:
fuente
Intenta seguir el código que me funciona:
fuente