when metodos method funciones example create await async c# .net asynchronous async-await dotnet-httpclient

c# - metodos - public async await



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

Edit: esta pregunta parece que podría ser el mismo problema, pero no tiene respuestas ...

Edición: en el caso de prueba 5, la tarea parece estar atascada en el estado WaitingForActivation .

Encontré algún comportamiento extraño al usar System.Net.Http.HttpClient en .NET 4.5 - donde "esperar" el resultado de una llamada a (por ejemplo) httpClient.GetAsync(...) nunca volverá.

Esto solo ocurre en ciertas circunstancias cuando se utiliza la nueva funcionalidad de lenguaje async / await y la API de tareas: el código siempre parece funcionar cuando se usan solo continuaciones.

Aquí hay algunos códigos que reproducen el problema: colóquelo en un nuevo "Proyecto MVC 4 WebApi" en Visual Studio 11 para exponer los siguientes puntos finales GET:

/api/test1 /api/test2 /api/test3 /api/test4 /api/test5 <--- never completes /api/test6

Cada uno de los puntos finales aquí devuelve los mismos datos (los encabezados de respuesta de stackoverflow.com) a excepción de /api/test5 que nunca se completa.

¿He encontrado un error en la clase HttpClient o estoy haciendo un mal uso de la API de alguna manera?

Código para reproducir:

public class BaseApiController : ApiController { /// <summary> /// Retrieves data using continuations /// </summary> protected Task<string> Continuations_GetSomeDataAsync() { var httpClient = new HttpClient(); var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead); return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString()); } /// <summary> /// Retrieves data using async/await /// </summary> protected async Task<string> AsyncAwait_GetSomeDataAsync() { var httpClient = new HttpClient(); var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead); return result.Content.Headers.ToString(); } } public class Test1Controller : BaseApiController { /// <summary> /// Handles task using Async/Await /// </summary> public async Task<string> Get() { var data = await Continuations_GetSomeDataAsync(); return data; } } public class Test2Controller : BaseApiController { /// <summary> /// Handles task by blocking the thread until the task completes /// </summary> public string Get() { var task = Continuations_GetSomeDataAsync(); var data = task.GetAwaiter().GetResult(); return data; } } public class Test3Controller : BaseApiController { /// <summary> /// Passes the task back to the controller host /// </summary> public Task<string> Get() { return Continuations_GetSomeDataAsync(); } } public class Test4Controller : BaseApiController { /// <summary> /// Handles task using Async/Await /// </summary> public async Task<string> Get() { var data = await AsyncAwait_GetSomeDataAsync(); return data; } } public class Test5Controller : BaseApiController { /// <summary> /// Handles task by blocking the thread until the task completes /// </summary> public string Get() { var task = AsyncAwait_GetSomeDataAsync(); var data = task.GetAwaiter().GetResult(); return data; } } public class Test6Controller : BaseApiController { /// <summary> /// Passes the task back to the controller host /// </summary> public Task<string> Get() { return AsyncAwait_GetSomeDataAsync(); } }


Edición: Generalmente, trate de evitar hacer lo siguiente, excepto como un último esfuerzo de zanja para evitar puntos muertos. Lee el primer comentario de Stephen Cleary.

Solución rápida desde here . 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;

Desde la fuente (editado para que coincida con el ejemplo anterior):

AsyncOperation ahora se invocará en ThreadPool, donde no habrá un SynchronizationContext, y las continuaciones usadas dentro de AsyncOperation no serán forzadas a regresar al hilo de invocación.

Para mí, esto parece una opción útil, ya que no tengo la opción de hacerlo asíncrono (lo que preferiría).

De la fuente:

Asegúrate de que la espera en el método FooAsync no encuentre un contexto al que volver. La forma más sencilla de hacerlo es invocar el trabajo asíncrono desde ThreadPool, por ejemplo, envolviendo la invocación en un Task.Run, por ejemplo

int Sync () {return Task.Run (() => Library.FooAsync ()). Resultado; }

FooAsync ahora se invocará en ThreadPool, donde no habrá un SynchronizationContext, y las continuaciones usadas dentro de FooAsync no serán forzadas a regresar al hilo que está invocando Sync ().


Estás haciendo mal uso de la API.

Aquí está la situación: en ASP.NET, solo un hilo puede manejar una solicitud a la vez. Puede hacer un procesamiento paralelo si es necesario (pedir prestados subprocesos adicionales del grupo de subprocesos), pero solo un subproceso tendrá el contexto de solicitud (los subprocesos adicionales no tienen el contexto de solicitud).

Esto es administrado por ASP.NET SynchronizationContext .

De forma predeterminada, cuando await una Task , el método se reanuda en un SynchronizationContext capturado (o un TaskScheduler capturado, si no hay un SynchronizationContext ). Normalmente, esto es justo lo que desea: una acción de controlador asíncrono await algo y, cuando se reanude, se reanudará con el contexto de la solicitud.

Entonces, aquí está la razón por la cual test5 falla:

  • Test5Controller.Get ejecuta AsyncAwait_GetSomeDataAsync (dentro del contexto de solicitud ASP.NET).
  • AsyncAwait_GetSomeDataAsync ejecuta HttpClient.GetAsync (dentro del contexto de solicitud ASP.NET).
  • La solicitud HTTP se envía y HttpClient.GetAsync devuelve una Task incompleta.
  • AsyncAwait_GetSomeDataAsync espera la Task ; ya que no está completo, AsyncAwait_GetSomeDataAsync devuelve una Task incompleta.
  • Test5Controller.Get bloquea el subproceso actual hasta que se complete la Task .
  • La respuesta HTTP entra, y la Task devuelta por HttpClient.GetAsync se completa.
  • AsyncAwait_GetSomeDataAsync intenta reanudarse dentro del contexto de solicitud ASP.NET. Sin embargo, ya existe un subproceso en ese contexto: el subproceso bloqueado en Test5Controller.Get .
  • Punto muerto.

He aquí por qué los otros trabajan:

  • ( test1 , test2 y test3 ): Continuations_GetSomeDataAsync programa la continuación al grupo de subprocesos, fuera del contexto de solicitud ASP.NET. Esto permite que la Task devuelta por Continuations_GetSomeDataAsync complete sin tener que volver a entrar en el contexto de la solicitud.
  • ( test4 y test6 ): como se espera la Task , no se bloquea el subproceso de solicitud ASP.NET. Esto permite que AsyncAwait_GetSomeDataAsync use el contexto de solicitud ASP.NET cuando esté listo para continuar.

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

  1. En los métodos async su "biblioteca", use ConfigureAwait(false) siempre que sea posible. En su caso, esto cambiaría AsyncAwait_GetSomeDataAsync para que sea var result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. No bloquee en la Task s; Es async hasta el fondo. En otras palabras, use Task.Result lugar de GetResult ( Task.Result y Task.Wait también deben reemplazarse con await ).

De esa manera, obtiene ambos beneficios: la continuación (el resto del método AsyncAwait_GetSomeDataAsync ) se ejecuta en un subproceso de grupo de subprocesos básico que no tiene que ingresar al contexto de solicitud ASP.NET; y el controlador en sí es async (que no bloquea un subproceso de solicitud).

Más información:

Actualización 2012-07-13: Incorporé esta respuesta en una publicación de blog .


Estas dos escuelas no son realmente excluyentes.

Aquí está el escenario donde simplemente tienes que usar

Task.Run(() => AsyncOperation()).Wait();

o algo como

AsyncContext.Run(AsyncOperation);

Tengo una acción MVC que está bajo el atributo de transacción de la base de datos. La idea era (probablemente) deshacer todo lo que se hace en la acción si algo sale mal. Esto no permite el cambio de contexto, de lo contrario, la reversión de la transacción o la confirmación fallarán.

La biblioteca que necesito es async, ya que se espera que se ejecute async.

La única opción. Ejecutarlo como una llamada de sincronización normal.

Solo estoy diciendo a cada uno lo suyo.


Estoy buscando aquí:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx

Y aquí:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx

Y viendo:

Este tipo y sus miembros están destinados a ser utilizados por el compilador.

Teniendo en cuenta que la versión await funciona, y es la forma "correcta" de hacer las cosas, ¿realmente necesita una respuesta a esta pregunta?

Mi voto es: mal uso de la API .


Ya que está utilizando .Result o .Wait o await esto terminará causando un punto muerto en su código.

puede usar ConfigureAwait(false) en métodos async para prevenir el interbloqueo

Me gusta esto:

var result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);

puede utilizar ConfigureAwait(false) siempre que sea posible para No bloquear el código asíncrono.