Tengo un public async void Foo()
método al que quiero llamar desde un método sincrónico. Hasta ahora, todo lo que he visto en la documentación de MSDN es llamar a métodos asincrónicos a través de métodos asincrónicos, pero todo mi programa no está construido con métodos asincrónicos.
¿Es esto posible?
Aquí hay un ejemplo de llamar a estos métodos desde un método asincrónico: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
Ahora estoy buscando llamar a estos métodos asincrónicos desde métodos de sincronización.
c#
async-await
Torre
fuente
fuente
async void Foo()
método no devuelve unTask
, significa que la persona que llama no puede saber cuándo se completa, debe devolverloTask
.Respuestas:
La programación asincrónica "crece" a través de la base de código. Se ha comparado con un virus zombie . La mejor solución es permitir que crezca, pero a veces eso no es posible.
He escrito algunos tipos en mi biblioteca Nito.AsyncEx para tratar con una base de código parcialmente asíncrono. Sin embargo, no hay una solución que funcione en cada situación.
Solución A
Si tiene un método asincrónico simple que no necesita sincronizarse de nuevo a su contexto, puede usar
Task.WaitAndUnwrapException
:Usted no desea utilizar
Task.Wait
oTask.Result
porque se envuelven en excepcionesAggregateException
.Esta solución solo es apropiada si
MyAsyncMethod
no se sincroniza de nuevo con su contexto. En otras palabras, cadaawait
enMyAsyncMethod
debe terminar conConfigureAwait(false)
. Esto significa que no puede actualizar ningún elemento de la interfaz de usuario ni acceder al contexto de solicitud de ASP.NET.Solución B
Si
MyAsyncMethod
necesita sincronizarse nuevamente con su contexto, entonces puede usarloAsyncContext.RunTask
para proporcionar un contexto anidado:* Actualización 14/04/2014: en versiones más recientes de la biblioteca, la API es la siguiente:
(Está bien usarlo
Task.Result
en este ejemplo porqueRunTask
propagaráTask
excepciones).La razón que puede necesitar en
AsyncContext.RunTask
lugar de estoTask.WaitAndUnwrapException
se debe a la posibilidad de un punto muerto bastante sutil que ocurre en WinForms / WPF / SL / ASP.NET:Task
.Task
.async
método usaawait
sinConfigureAwait
.Task
puede completar en esta situación porque solo se completa cuando finaliza elasync
método; elasync
método no puede completarse porque está intentando programar su continuación para elSynchronizationContext
, y WinForms / WPF / SL / ASP.NET no permitirá que se ejecute la continuación porque el método síncrono ya se está ejecutando en ese contexto.Esta es una de las razones por las cuales es una buena idea usar lo más posible
ConfigureAwait(false)
en cadaasync
método.Solución C
AsyncContext.RunTask
No funcionará en todos los escenarios. Por ejemplo, si elasync
método espera algo que requiera que se complete un evento de IU, entonces estará en punto muerto incluso con el contexto anidado. En ese caso, puede iniciar elasync
método en el grupo de subprocesos:Sin embargo, esta solución requiere una
MyAsyncMethod
que funcione en el contexto del grupo de subprocesos. Por lo tanto, no puede actualizar los elementos de la interfaz de usuario ni acceder al contexto de solicitud de ASP.NET. Y en ese caso, también puede agregarConfigureAwait(false)
a susawait
declaraciones y usar la solución A.Actualización, 01/05/2019: Las "prácticas menos peores" actuales se encuentran en un artículo de MSDN aquí .
fuente
WaitAndUnwrapException
es mi propio método de mi biblioteca AsyncEx . Las bibliotecas oficiales de .NET no proporcionan mucha ayuda para mezclar código de sincronización y asíncrono (y, en general, ¡no deberías hacerlo!). Estoy esperando .NET 4.5 RTW y una nueva computadora portátil que no sea XP antes de actualizar AsyncEx para que se ejecute en 4.5 (actualmente no puedo desarrollar para 4.5 porque estoy atascado en XP por unas pocas semanas más).AsyncContext
ahora tiene unRun
método que toma una expresión lambda, por lo que debería usarlovar result = AsyncContext.Run(() => MyAsyncMethod());
RunTask
método. Lo más cercano que pude encontrar fueRun
, pero eso no tiene unaResult
propiedad.var result = AsyncContext.Run(MyAsyncMethod);
Agregar una solución que finalmente resolvió mi problema, con suerte ahorrará el tiempo de alguien.
Primero lea un par de artículos de Stephen Cleary :
De las "dos mejores prácticas" en "No bloquear en código asíncrono", la primera no funcionó para mí y la segunda no era aplicable (básicamente si puedo usar
await
, lo hago!).Así que aquí está mi solución: envuelva la llamada dentro de
Task.Run<>(async () => await FunctionAsync());
y, con suerte, sin punto muerto .Aquí está mi código:
fuente
Task.Run()
no es una práctica recomendada en un código asíncrono. Pero, de nuevo, ¿cuál es la respuesta a la pregunta original? ¿Nunca llamar a un método asíncrono sincrónicamente? Deseamos, pero en un mundo real, a veces tenemos que hacerlo.Parallel.ForEach
abuso no tendrá un efecto en 'el mundo real' y, finalmente, derribó los servidores. Este código está bien para aplicaciones de consola, pero como dice @ChrisPratt, no debe usarse en aplicaciones web. Puede funcionar "ahora" pero no es escalable.Microsoft creó una clase AsyncHelper (interna) para ejecutar Async como Sync. La fuente se ve así:
Las clases base Microsoft.AspNet.Identity solo tienen métodos Async y para llamarlas como Sync hay clases con métodos de extensión similares (ejemplo de uso):
Para aquellos preocupados por los términos de licencia del código, aquí hay un enlace a un código muy similar (solo agrega soporte para la cultura en el hilo) que tiene comentarios para indicar que tiene licencia MIT de Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
fuente
await
llamadas conConfigureAwait(false)
. Intenté usarAsyncHelper.RunSync
para llamar a una función asincrónica desde laApplication_Start()
función en Global.asax y parece funcionar. ¿Significa esto que noAsyncHelper.RunSync
es confiablemente propenso al problema de punto muerto de "volver al contexto del interlocutor" que leí en otra parte de esta publicación?async Main ahora forma parte de C # 7.2 y se puede habilitar en la configuración de compilación avanzada del proyecto.
Para C # <7.2, la forma correcta es:
Verá esto usado en mucha documentación de Microsoft, por ejemplo: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- temas-suscripciones
fuente
MainAsync().Wait()
?Lee la palabra clave 'esperar' como "iniciar esta tarea de ejecución prolongada y luego devolver el control al método de llamada". Una vez que se realiza la tarea de larga duración, se ejecuta el código después. El código después de la espera es similar a lo que solían ser los métodos de CallBack. La gran diferencia es que el flujo lógico no se interrumpe, lo que hace que sea mucho más fácil escribir y leer.
fuente
Wait
envuelve excepciones y tiene la posibilidad de un punto muerto.await
, se ejecutaría sincrónicamente. Al menos eso funciona para mí (sin llamarmyTask.Wait
). En realidad, recibí una excepción cuando intenté llamarmyTask.RunSynchronously()
porque ya se había ejecutado..Result
.Result
llamada está bloqueando en ese momento, por lo que nunca llega allí. YResult
nunca termina, porque está esperando a alguien que está esperandoResult
que termine, básicamente: DNo estoy 100% seguro, pero creo que la técnica descrita en este blog debería funcionar en muchas circunstancias:
fuente
Sin embargo, hay una buena solución que funciona en (casi: ver comentarios) en cada situación: una bomba de mensajes ad-hoc (SynchronizationContext).
El subproceso de llamada se bloqueará como se esperaba, a la vez que se asegurará de que todas las continuas llamadas desde la función asincrónica no se bloqueen, ya que se ordenarán al SynchronizationContext ad-hoc (bomba de mensajes) que se ejecuta en el subproceso de llamada.
El código del asistente de bomba de mensajes ad-hoc:
Uso:
Una descripción más detallada de la bomba asíncrona está disponible aquí .
fuente
Para cualquiera que esté prestando atención a esta pregunta ...
Si miras adentro
Microsoft.VisualStudio.Services.WebApi
hay una clase llamadaTaskExtensions
. Dentro de esa clase verás el método de extensión estáticaTask.SyncResult()
, que simplemente bloquea el hilo hasta que la tarea regrese.Internamente llama, lo
task.GetAwaiter().GetResult()
cual es bastante simple, sin embargo, está sobrecargado para funcionar en cualquierasync
método que regreseTask
,Task<T>
oTask<HttpResponseMessage>
... azúcar sintáctica, bebé ... papá tiene un gusto por lo dulce.Parece que
...GetAwaiter().GetResult()
es la forma oficial de MS de ejecutar código asíncrono en un contexto de bloqueo. Parece funcionar muy bien para mi caso de uso.fuente
O usa esto:
fuente
Puede llamar a cualquier método asincrónico desde un código síncrono, es decir, hasta que lo necesite
await
, en cuyo caso también deben marcarseasync
.Como muchas personas están sugiriendo aquí, puede llamar a Wait () o Result en la tarea resultante en su método sincrónico, pero luego termina con una llamada de bloqueo en ese método, que de alguna manera anula el propósito de async.
Si realmente no puede hacer su método
async
y no desea bloquear el método sincrónico, entonces tendrá que usar un método de devolución de llamada pasándolo como parámetro al método ContinueWith en la tarea.fuente
async
" desvió mi atención de lo que realmente estabas diciendo.Sé que llego muy tarde. Pero en caso de que alguien como yo quisiera resolver esto de una manera ordenada, fácil y sin depender de otra biblioteca.
Encontré el siguiente código de Ryan
entonces puedes llamarlo así
fuente
Después de horas de probar diferentes métodos, con más o menos éxito, esto es con lo que terminé. No termina en un punto muerto mientras se obtiene el resultado y también obtiene y lanza la excepción original y no la envuelta.
fuente
Podría llamarse desde un nuevo hilo (¡NO desde el grupo de hilos!):
fuente
Esos métodos asincrónicos de Windows tienen un pequeño método ingenioso llamado AsTask (). Puede usar esto para que el método se devuelva como una tarea, de modo que pueda llamar manualmente a Wait () en él.
Por ejemplo, en una aplicación de Windows Phone 8 Silverlight, puede hacer lo siguiente:
¡Espero que esto ayude!
fuente
Si quieres ejecutarlo, sincroniza
fuente
RunSynchronously()
a una tarea caliente resultados a unInvalidOperationException
. Pruébelo con este código:Task.Run(() => {}).RunSynchronously();
fuente