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.Waito Task.Resultporque se envuelven en excepciones AggregateException.
Esta solución solo es apropiada si MyAsyncMethodno se sincroniza de nuevo con su contexto. En otras palabras, cada awaiten MyAsyncMethoddebe 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 MyAsyncMethodnecesita sincronizarse nuevamente con su contexto, entonces puede usarlo AsyncContext.RunTaskpara 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.Resulten este ejemplo porque RunTaskpropagará Taskexcepciones).
La razón que puede necesitar en AsyncContext.RunTasklugar de esto Task.WaitAndUnwrapExceptionse 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
asyncmétodo usaawait sin ConfigureAwait.
- No se
Taskpuede completar en esta situación porque solo se completa cuando finaliza el asyncmétodo; el asyncmé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 asyncmétodo.
Solución C
AsyncContext.RunTaskNo funcionará en todos los escenarios. Por ejemplo, si el asyncmé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 asyncmétodo en el grupo de subprocesos:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Sin embargo, esta solución requiere una MyAsyncMethodque 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 awaitdeclaraciones 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í .