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
ejecutaAsyncAwait_GetSomeDataAsync
(dentro del contexto de solicitud ASP.NET). -
AsyncAwait_GetSomeDataAsync
ejecutaHttpClient.GetAsync
(dentro del contexto de solicitud ASP.NET). - La solicitud HTTP se envía y
HttpClient.GetAsync
devuelve unaTask
incompleta. -
AsyncAwait_GetSomeDataAsync
espera laTask
; ya que no está completo,AsyncAwait_GetSomeDataAsync
devuelve unaTask
incompleta. -
Test5Controller.Get
bloquea el subproceso actual hasta que se complete laTask
. - La respuesta HTTP entra, y la
Task
devuelta porHttpClient.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 enTest5Controller.Get
. - Punto muerto.
He aquí por qué los otros trabajan:
- (
test1
,test2
ytest3
):Continuations_GetSomeDataAsync
programa la continuación al grupo de subprocesos, fuera del contexto de solicitud ASP.NET. Esto permite que laTask
devuelta porContinuations_GetSomeDataAsync
complete sin tener que volver a entrar en el contexto de la solicitud. - (
test4
ytest6
): como se espera laTask
, no se bloquea el subproceso de solicitud ASP.NET. Esto permite queAsyncAwait_GetSomeDataAsync
use el contexto de solicitud ASP.NET cuando esté listo para continuar.
Y aquí están las mejores prácticas:
- En los métodos
async
su "biblioteca", useConfigureAwait(false)
siempre que sea posible. En su caso, esto cambiaríaAsyncAwait_GetSomeDataAsync
para que seavar result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
- No bloquee en la
Task
s; Esasync
hasta el fondo. En otras palabras, useTask.Result
lugar deGetResult
(Task.Result
yTask.Wait
también deben reemplazarse conawait
).
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:
- Mi post de introducción de
async
/ await, que incluye una breve descripción de cómo los solicitantes deTask
usanSynchronizationContext
. - Las preguntas frecuentes de Async / Await , que se detallan más en los contextos. ¡También vea Await, y UI, y puntos muertos! ¡Oh mi! lo que se aplica aquí aunque esté en ASP.NET en lugar de en una interfaz de usuario, ya que ASP.NET
SynchronizationContext
restringe el contexto de la solicitud a solo un hilo a la vez. - Esta publicación del foro de MSDN .
- Stephen Toub demuestra este punto muerto (usando una interfaz de usuario) , y también Lucian Wischik .
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í:
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.