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
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Usted no desea utilizar Task.Wait
o Task.Result
porque se envuelven en excepciones AggregateException
.
Esta solución solo es apropiada si MyAsyncMethod
no se sincroniza de nuevo con su contexto. En otras palabras, cada await
en MyAsyncMethod
debe terminar con ConfigureAwait(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 usarlo AsyncContext.RunTask
para proporcionar un contexto anidado:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* Actualización 14/04/2014: en versiones más recientes de la biblioteca, la API es la siguiente:
var result = AsyncContext.Run(MyAsyncMethod);
(Está bien usarlo Task.Result
en este ejemplo porque RunTask
propagará Task
excepciones).
La razón que puede necesitar en AsyncContext.RunTask
lugar de esto Task.WaitAndUnwrapException
se debe a la posibilidad de un punto muerto bastante sutil que ocurre en WinForms / WPF / SL / ASP.NET:
- Un método síncrono llama a un método asíncrono, obteniendo a
Task
.
- El método sincrónico hace una espera de bloqueo en el
Task
.
- El
async
método usaawait
sin ConfigureAwait
.
- No se
Task
puede completar en esta situación porque solo se completa cuando finaliza el async
método; el async
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 cada async
método.
Solución C
AsyncContext.RunTask
No funcionará en todos los escenarios. Por ejemplo, si el async
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 el async
método en el grupo de subprocesos:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
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 agregar ConfigureAwait(false)
a sus await
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í .