Hola, descubrimos la respuesta a lo que estabas buscando, desplázate y la verás a continuación.
Solución:
Estás haciendo un mal uso de la API.
Aquí está la situación: en ASP.NET, solo un subproceso puede manejar una solicitud a la vez. Puede realizar un procesamiento paralelo si es necesario (tomando prestados subprocesos adicionales del grupo de subprocesos), pero solo un subproceso tendría el contexto de solicitud (los subprocesos adicionales no tienen el contexto de solicitud).
Esto es administrado por ASP.NET SynchronizationContext
.
Por defecto, cuando Ud. await
a Task
el método se reanuda en una captura SynchronizationContext
(o un capturado TaskScheduler
si no hay SynchronizationContext
). Normalmente, esto es justo lo que desea: una acción de controlador asincrónico await
algo, y cuando se reanuda, se reanuda con el contexto de solicitud.
Entonces, he aquí por qué test5
falla:
Test5Controller.Get
ejecutaAsyncAwait_GetSomeDataAsync
(dentro del contexto de solicitud de ASP.NET).AsyncAwait_GetSomeDataAsync
ejecutaHttpClient.GetAsync
(dentro del contexto de solicitud de ASP.NET).- La solicitud HTTP se envía y
HttpClient.GetAsync
devuelve un incompletoTask
. AsyncAwait_GetSomeDataAsync
espera elTask
; ya que no esta completoAsyncAwait_GetSomeDataAsync
devuelve un incompletoTask
.Test5Controller.Get
bloques el hilo actual hasta esoTask
completa- Entra la respuesta HTTP y el
Task
devuelto porHttpClient.GetAsync
esta completado. AsyncAwait_GetSomeDataAsync
intenta reanudar dentro del contexto de solicitud de ASP.NET. Sin embargo, ya hay un hilo en ese contexto: el hilo bloqueado enTest5Controller.Get
.- Punto muerto.
He aquí por qué los otros funcionan:
- (
test1
,test2
ytest3
):Continuations_GetSomeDataAsync
programa la continuación del grupo de subprocesos, fuera de el contexto de solicitud de ASP.NET. Esto permite que elTask
devuelto porContinuations_GetSomeDataAsync
para completar sin tener que volver a ingresar el contexto de la solicitud. - (
test4
ytest6
): Desde elTask
es esperado, el subproceso de solicitud de ASP.NET no está bloqueado. Esto permiteAsyncAwait_GetSomeDataAsync
para usar el contexto de solicitud de ASP.NET cuando esté listo para continuar.
Y aquí están las mejores prácticas:
- En tu “biblioteca”
async
métodos, usoConfigureAwait(false)
cuando sea posible. En tu caso, esto cambiaría.AsyncAwait_GetSomeDataAsync
servar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
- no bloquees
Task
s; esasync
toda la calle abajo. En otras palabras, usaawait
en lugar deGetResult
(Task.Result
yTask.Wait
también debe ser reemplazado porawait
).
De esa manera, obtiene ambos beneficios: la continuación (el resto de la AsyncAwait_GetSomeDataAsync
method) se ejecuta en un subproceso de grupo de subprocesos básico que no tiene que ingresar al contexto de solicitud de ASP.NET; y el propio controlador es async
(que no bloquea un hilo de solicitud).
Más información:
- Mi
async
/await
publicación de introducción, que incluye una breve descripción de cómoTask
uso de los camarerosSynchronizationContext
. - Preguntas frecuentes sobre Async/Await, que ofrece más detalles sobre los contextos. ¡Vea también Await, UI y interbloqueos! ¡Oh mi! cual lo hace aplicar aquí aunque esté en ASP.NET en lugar de una interfaz de usuario, porque ASP.NET
SynchronizationContext
restringe el contexto de la solicitud a un solo hilo a la vez. - Esta publicación en el foro de MSDN.
- Stephen Toub demuestra este punto muerto (utilizando una interfaz de usuario) y también lo hace Lucian Wischik.
Actualización 2012-07-13: Incorporé esta respuesta en una publicación de blog.
Editar: en general, trate de evitar hacer lo siguiente, excepto como último esfuerzo para evitar puntos muertos. Lea el primer comentario de Stephen Cleary.
Solución rápida desde aquí. En lugar de escribir:
Task tsk = AsyncOperation();
tsk.Wait();
Tratar:
Task.Run(() => AsyncOperation()).Wait();
O si necesitas un resultado:
var result = Task.Run(() => AsyncOperation()).Result;
De la fuente (editado para que coincida con el ejemplo anterior):
AsyncOperation ahora se invocará en ThreadPool, donde no habrá un SynchronizationContext, y las continuaciones utilizadas dentro de AsyncOperation no se verán obligadas a volver al subproceso de invocación.
Para mí, esto parece una opción utilizable ya que no tengo la opción de sincronizarlo completamente (lo que preferiría).
De la fuente:
Asegúrese de que await en el método FooAsync no encuentre un contexto al que volver a ordenar. La forma más sencilla de hacerlo es invocar el trabajo asíncrono desde ThreadPool, como envolviendo la invocación en Task.Run, por ejemplo
int Sync() return Task.Run(() => Library.FooAsync()).Result;
FooAsync ahora se invocará en ThreadPool, donde no habrá un SynchronizationContext, y las continuaciones utilizadas dentro de FooAsync no se verán obligadas a regresar al subproceso que invoca Sync().
Ya que estás usando .Result
o .Wait
o await
esto acabará provocando un punto muerto en tu código.
puedes usar ConfigureAwait(false)
en async
métodos para prevención de estancamiento
Me gusta esto:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
puedes usar
ConfigureAwait(false)
siempre que sea posible para Don’t Block Async Code .
Si tienes algún dificultad o forma de refinar nuestro ensayo eres capaz de realizar una crítica y con deseo lo leeremos.