Tengo una aplicación .Net 4.5 de varios niveles que llama a un método que usa las palabras clave nuevas asyncy las awaitpalabras clave de C # que simplemente se cuelgan y no veo por qué.
En la parte inferior tengo un método asincrónico que amplía nuestra utilidad de base de datos OurDBConn(básicamente un contenedor para el subyacente DBConnectiony los DBCommandobjetos):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Luego tengo un método asincrónico de nivel medio que llama a esto para obtener algunos totales de ejecución lenta:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Finalmente tengo un método de UI (una acción MVC) que se ejecuta sincrónicamente:
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
El problema es que se cuelga de esa última línea para siempre. Hace lo mismo si llamo asyncTask.Wait(). Si ejecuto el método SQL lento directamente, tarda unos 4 segundos.
El comportamiento que espero es que cuando llegue asyncTask.Result, si no está terminado, debe esperar hasta que lo esté, y una vez que lo esté, debe devolver el resultado.
Si paso con un depurador, la declaración SQL se completa y la función lambda finaliza, pero nunca se llega a la return result;línea de GetTotalAsync.
¿Alguna idea de lo que estoy haciendo mal?
¿Alguna sugerencia sobre dónde debo investigar para solucionar este problema?
¿Podría ser un punto muerto en alguna parte y, de ser así, hay alguna forma directa de encontrarlo?

SynchronizationContext.async/await.Este es el
asyncescenario clásico de punto muerto mixto , como describo en mi blog . Jason lo describió bien: de forma predeterminada, se guarda un "contexto" en cada unoawaity se utiliza para continuar con elasyncmétodo. Este "contexto" es el actual aSynchronizationContextmenos que lo seanull, en cuyo caso es el actualTaskScheduler. Cuando elasyncmétodo intenta continuar, primero vuelve a entrar en el "contexto" capturado (en este caso, un ASP.NETSynchronizationContext). ASP.NETSynchronizationContextsolo permite un hilo en el contexto a la vez, y ya hay un hilo en el contexto: el hilo bloqueadoTask.Result.Hay dos pautas que evitarán este punto muerto:
asynctodo el camino hacia abajo. Mencionas que "no puedes" hacer esto, pero no estoy seguro de por qué no. ASP.NET MVC en .NET 4.5 ciertamente puede admitirasyncacciones, y no es un cambio difícil de realizar.ConfigureAwait(continueOnCapturedContext: false)tanto como sea posible. Esto anula el comportamiento predeterminado de reanudar en el contexto capturado.fuente
ConfigureAwait(false)Garantiza que la función actual se reanude en un contexto diferente?asyncacción sin romper la forma en que esto funciona del lado del cliente. Sin embargo, ciertamente planeo investigar esa opción a más largo plazo.ConfigureAwait(false)el árbol de llamadas habría resuelto el problema del OP.asyncno afecta en absoluto al lado del cliente. Explico esto en otra publicación de blog,asyncNo cambia el protocolo HTTP .asyncque "crezca" a través del código base. Si su método de controlador puede depender de operaciones asincrónicas, entonces el método de la clase base debería regresarTask<ActionResult>. La transición de un proyecto grande aasyncsiempre es incómoda porque mezclarasyncy sincronizar código es difícil y complicado. Elasynccódigo puro es mucho más simple.Estaba en la misma situación de punto muerto, pero en mi caso llamando a un método asíncrono desde un método de sincronización, lo que me funciona fue:
¿Es este un buen enfoque, alguna idea?
fuente
Solo para agregar a la respuesta aceptada (no hay suficiente representante para comentar), tuve este problema al bloquear el uso
task.Result, aunque todos los eventos que aparecen aawaitcontinuaciónConfigureAwait(false), como en este ejemplo:El problema en realidad reside en el código de la biblioteca externa. El método de la biblioteca asincrónica intentó continuar en el contexto de sincronización de llamada, sin importar cómo configuré la espera, lo que llevó a un punto muerto.
Por lo tanto, la respuesta fue lanzar mi propia versión del código de la biblioteca externa
ExternalLibraryStringAsync, para que tuviera las propiedades de continuación deseadas.respuesta incorrecta para propósitos históricos
Después de mucho dolor y angustia, encontré la solución enterrada en esta publicación de blog (Ctrl-f para 'punto muerto'). Gira en torno al uso
task.ContinueWith, en lugar de al desnudotask.Result.Ejemplo de interbloqueo anterior:
Evite el punto muerto como este:
fuente
Taskse haya completado y no le está proporcionando a la persona que llama ningún medio para determinar cuándo ocurre realmente la mutación del objeto devuelto.GetFooSynchronousmétodo?Tasklugar de bloquear.respuesta rápida: cambie esta línea
a
¿por qué? no debe usar .result para obtener el resultado de las tareas dentro de la mayoría de las aplicaciones, excepto las aplicaciones de consola, si lo hace, su programa se bloqueará cuando llegue allí
también puede probar el siguiente código si desea utilizar .Result
fuente