Saltar al contenido

HttpClient.GetAsync(…) nunca regresa cuando se usa await/async

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 Taskel método se reanuda en una captura SynchronizationContext (o un capturado TaskSchedulersi 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 ejecuta AsyncAwait_GetSomeDataAsync (dentro del contexto de solicitud de ASP.NET).
  • AsyncAwait_GetSomeDataAsync ejecuta HttpClient.GetAsync (dentro del contexto de solicitud de ASP.NET).
  • La solicitud HTTP se envía y HttpClient.GetAsync devuelve un incompleto Task.
  • AsyncAwait_GetSomeDataAsync espera el Task; ya que no esta completo AsyncAwait_GetSomeDataAsync devuelve un incompleto Task.
  • Test5Controller.Getbloques el hilo actual hasta eso Task completa
  • Entra la respuesta HTTP y el Task devuelto por HttpClient.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 en Test5Controller.Get.
  • Punto muerto.

He aquí por qué los otros funcionan:

  • (test1, test2y test3): Continuations_GetSomeDataAsync programa la continuación del grupo de subprocesos, fuera de el contexto de solicitud de ASP.NET. Esto permite que el Task devuelto por Continuations_GetSomeDataAsync para completar sin tener que volver a ingresar el contexto de la solicitud.
  • (test4 y test6): Desde el Task es esperado, el subproceso de solicitud de ASP.NET no está bloqueado. Esto permite AsyncAwait_GetSomeDataAsync para usar el contexto de solicitud de ASP.NET cuando esté listo para continuar.

Y aquí están las mejores prácticas:

  1. En tu “biblioteca” async métodos, uso ConfigureAwait(false) cuando sea posible. En tu caso, esto cambiaría. AsyncAwait_GetSomeDataAsync ser var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. no bloquees Tasks; es async toda la calle abajo. En otras palabras, usa await en lugar de GetResult (Task.Result y Task.Wait también debe ser reemplazado por await).

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ómo Task uso de los camareros SynchronizationContext.
  • 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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *