Tengo una aplicación .Net 4.5 de varios niveles que llama a un método que usa las palabras clave nuevas async
y las await
palabras 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 DBConnection
y los DBCommand
objetos):
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
async
escenario clásico de punto muerto mixto , como describo en mi blog . Jason lo describió bien: de forma predeterminada, se guarda un "contexto" en cada unoawait
y se utiliza para continuar con elasync
método. Este "contexto" es el actual aSynchronizationContext
menos que lo seanull
, en cuyo caso es el actualTaskScheduler
. Cuando elasync
método intenta continuar, primero vuelve a entrar en el "contexto" capturado (en este caso, un ASP.NETSynchronizationContext
). ASP.NETSynchronizationContext
solo 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:
async
todo 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 admitirasync
acciones, 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?async
acció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.async
no afecta en absoluto al lado del cliente. Explico esto en otra publicación de blog,async
No cambia el protocolo HTTP .async
que "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 aasync
siempre es incómoda porque mezclarasync
y sincronizar código es difícil y complicado. Elasync
có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 aawait
continuació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
Task
se 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.GetFooSynchronous
método?Task
lugar 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