La llamada asincrónica con await en HttpClient nunca regresa

95

Tengo una llamada que estoy haciendo desde dentro de una C#aplicación de metro basada en xaml en el Win8 CP; esta llamada simplemente llega a un servicio web y devuelve datos JSON.

HttpMessageHandler handler = new HttpClientHandler();

HttpClient httpClient = new HttpClient(handler);
httpClient.BaseAddress = new Uri("http://192.168.1.101/api/");

var result = await httpClient.GetStreamAsync("weeklyplan");
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(WeeklyPlanData[]));
return (WeeklyPlanData[])ser.ReadObject(result);

Se cuelga en el awaitpero la llamada http realmente regresa casi inmediatamente (confirmado a través de Fiddler) es como si awaitse ignorara y simplemente se cuelga allí.

Antes de preguntar , SÍ, la función de red privada está activada.

¿Alguna idea de por qué esto colgaría?

keithwarren7
fuente
1
¿Cómo llamas a ese asyncmétodo? ¿No lanza una excepción?
svick

Respuestas:

136

Mira esta respuesta a mi pregunta que parece ser muy similar.

Algo para probar: invocar ConfigureAwait(false)la tarea devuelta por GetStreamAsync(). P.ej

var result = await httpClient.GetStreamAsync("weeklyplan")
                             .ConfigureAwait(continueOnCapturedContext:false);

Si esto es útil o no depende de cómo se llame a su código anterior; en mi caso, llamar al asyncmétodo usando Task.GetAwaiter().GetResult()causó que el código se bloqueara.

Esto se debe a que GetResult()bloquea el hilo actual hasta que se completa la tarea. Cuando la tarea se completa, intenta volver a ingresar al contexto del hilo en el que se inició, pero no puede porque ya hay un hilo en ese contexto, que está bloqueado por la llamada a GetResult()... ¡punto muerto!

Esta publicación de MSDN detalla un poco cómo .NET sincroniza subprocesos paralelos, y la respuesta a mi propia pregunta brinda algunas de las mejores prácticas.

Benjamín Fox
fuente
12
Gracias, casi me rindo con async / await antes de ver esto.
Den
4
¡Yo también! ¿Por qué esto no está mejor documentado? Gracias de nuevo
Avrohom Yisroel
1
¿Va a suceder si no está en el contexto de la interfaz de usuario ni en el contexto de ASP.NET?
machinarium
1
¡Respuesta impresionante! Pero estoy confundido por qué hasta ahora solo tengo este problema cuando uso HttpClient, parece que la implementación subyacente dentro de HttpClient no está implementada correctamente. Las otras soluciones que he encontrado implican configurar el hilo actual como STA, lo que ayuda, pero es realmente indirecto, especialmente cuando está utilizando un ensamblaje de terceros y no es consciente de que, bajo el capó, alguna llamada va a esperar definitivamente una respuesta de que nunca va a conseguir. En mi caso, el dll era interno, por lo que pudimos ConfigureAwait ... pero tenía que hacerse en el nivel más bajo para el obj de HttpClient.
Chris Schaller
2
@ChrisSchaller Asegúrese de leer la respuesta completa en stackoverflow.com/a/10351400/174735 , que explica el problema de manera bastante completa.
Benjamin Fox
5

Solo un aviso: si pierde la espera en el nivel superior en un controlador ASP.NET, y devuelve la tarea en lugar del resultado como respuesta, en realidad simplemente se bloquea en las llamadas de espera anidadas sin errores. Un error tonto, pero si hubiera visto esta publicación, podría haberme ahorrado algo de tiempo revisando el código en busca de algo extraño.

bozzle
fuente
0

Descargo de responsabilidad: no me gusta la solución ConfigureAwait () porque la encuentro no intuitiva y difícil de recordar. En cambio, llegué a la conclusión de envolver las llamadas de método no esperadas en Task.Run (() => myAsyncMethodNotUsingAwait ()). ¡Esto parece funcionar al 100%, pero podría ser solo una condición de carrera !? Para ser honesto, no estoy tan seguro de lo que está pasando. Esta conclusión puede ser incorrecta y me arriesgo a que mis puntos StackOverflow aquí para aprender de los comentarios :-P. ¡Por favor léalos!

Acabo de tener el problema como se describe y encontré más información aquí .

La declaración es: "no puedes llamar a un método asincrónico"

await asyncmethod2()

de un método que bloquea

myAsyncMethod().Result

En mi caso, no pude cambiar el método de llamada y no fue asincrónico. Pero en realidad no me importaba el resultado. Según recuerdo, tampoco funcionó al eliminar el .Result y falta la espera.

Entonces hice esto:

public void Configure()
{
    var data = "my data";
    Task.Run(() => NotifyApi(data));
}

private async Task NotifyApi(bool data)
{
    var toSend = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
    await client.PostAsync("http://...", data);
}

En mi caso, no me importó el resultado de la llamada al método no asíncrono, pero supongo que es bastante común en este caso de uso. Puede utilizar el resultado en el método asincrónico de llamada.

Codificación de su vida
fuente