L'appel async avec await dans HttpClient ne retourne jamais

95

J'ai un appel que je passe depuis l'intérieur d'une C#application métro basée sur xaml sur le Win8 CP; cet appel atteint simplement un service Web et renvoie des données 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);

Il se bloque à la awaitmais l' appel http revient en fait presque immédiatement (confirmé par Fiddler); c'est comme si le awaitétait ignoré et qu'il s'y accrochait.

Avant de demander - OUI - la capacité de réseau privé est activée.

Des idées pourquoi cela se bloquerait?

keithwarren7
la source
1
Comment appelez-vous cette asyncméthode? Cela ne lève-t-il pas une exception?
svick

Réponses:

136

Découvrez cette réponse à ma question qui semble être très similaire.

Quelque chose à essayer: appelez ConfigureAwait(false)la tâche renvoyée par GetStreamAsync(). Par exemple

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

Que cela soit utile ou non dépend de la façon dont votre code ci-dessus est appelé - dans mon cas, l'appel de la asyncméthode utilisant a Task.GetAwaiter().GetResult()provoqué le blocage du code.

Cela est dû au fait que GetResult()bloque le thread actuel jusqu'à ce que la tâche se termine. Lorsque la tâche est terminée, elle tente de rentrer dans le contexte de thread dans lequel elle a été démarrée mais ne peut pas car il y a déjà un thread dans ce contexte, qui est bloqué par l'appel à GetResult()... deadlock!

Cet article MSDN donne quelques détails sur la façon dont .NET synchronise les threads parallèles - et la réponse donnée à ma propre question donne quelques bonnes pratiques.

Benjamin Fox
la source
12
Merci, presque abandonné async / wait avant de voir cela.
Den
4
Moi aussi! Pourquoi ce truc n'est-il pas mieux documenté? Merci encore
Avrohom Yisroel
1
Cela se produira-t-il si ce n'est sur le contexte d'interface utilisateur et le contexte ASP.NET?
machinarium
1
Réponse géniale! Mais je ne comprends pas pourquoi jusqu'à présent je n'ai ce problème que lors de l'utilisation de HttpClient, il semble que l'implémentation sous-jacente dans HttpClient ne soit pas correctement implémentée. Les autres solutions de contournement que j'ai trouvées impliquent de définir le thread actuel comme STA, ce qui aide mais est vraiment indirect, surtout lorsque vous utilisez un assemblage tiers et que vous ne savez pas que sous le capot, un appel va certainement attendre une réponse. n'obtiendra jamais. Dans mon cas, la dll était en interne, donc nous avons pu ConfigureAwait ... mais cela devait être fait au niveau le plus bas de l'objet HttpClient.
Chris Schaller
2
@ChrisSchaller Assurez-vous de lire la réponse complète sur stackoverflow.com/a/10351400/174735 , ce qui explique le problème assez complètement.
Benjamin Fox
5

Juste un avertissement - si vous manquez l'attente au niveau supérieur dans un contrôleur ASP.NET et que vous renvoyez la tâche au lieu du résultat en tant que réponse, elle se bloque en fait dans le ou les appels d'attente imbriqués sans erreur. Une erreur stupide, mais si j'avais vu ce post, cela m'aurait peut-être fait gagner du temps en vérifiant le code pour quelque chose d'étrange.

bozzle
la source
0

Avertissement: je n'aime pas la solution ConfigureAwait () car je la trouve non intuitive et difficile à retenir. Au lieu de cela, je suis arrivé à la conclusion d'envelopper les appels de méthode non attendus dans Task.Run (() => myAsyncMethodNotUsingAwait ()). Cela semble fonctionner à 100% mais pourrait être juste une condition de course !? Je ne sais pas trop ce qui se passe pour être honnête. Cette conclusion pourrait être fausse et je risque mes points StackOverflow ici pour apprendre, espérons-le, des commentaires :-P. Veuillez les lire!

Je viens d'avoir le problème décrit et j'ai trouvé plus d'informations ici .

La déclaration est: "vous ne pouvez pas appeler une méthode asynchrone"

await asyncmethod2()

d'une méthode qui bloque

myAsyncMethod().Result

Dans mon cas, je ne pouvais pas changer la méthode d'appel et ce n'était pas asynchrone. Mais je ne me souciais pas vraiment du résultat. Si je me souviens bien, cela ne fonctionnait pas non plus en supprimant le .Result et en manquant l'attente.

Alors j'ai fait ceci:

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);
}

Dans mon cas, je ne me souciais pas du résultat de la méthode d'appel non asynchrone, mais je suppose que c'est assez courant dans ce cas d'utilisation. Vous pouvez utiliser le résultat dans la méthode asynchrone appelante.

Codage de votre vie
la source