在 HttpClient 中使用 await 的异步调用永远不会返回

Posted

技术标签:

【中文标题】在 HttpClient 中使用 await 的异步调用永远不会返回【英文标题】:Async call with await in HttpClient never returns 【发布时间】:2012-03-27 18:03:37 【问题描述】:

我在 Win8 CP 上基于 xaml 的C# metro 应用程序内部进行了调用;此调用只是访问一个 Web 服务并返回 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);

它挂在await,但 http 调用实际上几乎立即返回(通过提琴手确认);就好像await 被忽略了,它就挂在那里。

在您询问之前 - 是 - 专用网络功能已打开。

任何想法为什么会挂起?

【问题讨论】:

你怎么称呼async 方法?不会抛出异常吗? 【参考方案1】:

查看this answer 我的问题似乎非常相似。

尝试一下:在GetStreamAsync() 返回的任务上调用ConfigureAwait(false)。例如

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

这是否有用取决于您上面的代码是如何被调用的——在我的例子中,使用Task.GetAwaiter().GetResult() 调用async 方法会导致代码挂起。

这是因为GetResult() 阻塞当前线程,直到任务完成。当任务完成时,它会尝试重新进入启动它的线程上下文,但不能,因为该上下文中已经有一个线程,该线程被对GetResult()的调用阻塞... 死锁!

This MSDN post 详细介绍了 .NET 如何同步并行线程,the answer given to my own question 提供了一些最佳实践。

【讨论】:

谢谢,在看到这个之前几乎放弃了 async/await。 我也是!为什么没有更好地记录这些东西?再次感谢 如果它既不在 UI 上下文也不在 ASP.NET 上下文上会不会发生? 很棒的答案!但是我很困惑为什么到目前为止我在使用 HttpClient 时只有这个问题,似乎 HttpClient 中的底层实现没有正确实现。我发现的其他解决方法涉及将当前线程设置为 STA,这会有所帮助,但实际上是间接的,尤其是当您使用 3rd 方程序集并且不知道在后台某些调用肯定会等待它的响应时永远不会得到。在我的情况下,dll 是内部的,所以我们能够 ConfigureAwait... 但它必须在 HttpClient obj 的最低级别上完成。 @ChrisSchaller 请务必阅读***.com/a/10351400/174735 的完整答案,该答案相当完整地解释了这个问题。【参考方案2】:

请注意 - 如果您错过了 ASP.NET 控制器顶层的等待,并且您返回任务而不是结果作为响应,它实际上只是挂在嵌套的等待调用中没有错误。一个愚蠢的错误,但如果我看到这篇文章,它可能会节省我一些时间检查代码是否有奇怪的地方。

【讨论】:

不确定您的意思,每当我在非异步方法(控制器的方法)中使用 await 运算符时,它都会出错。我也无法将其更改为异步。 示例:public IHttpActionResult GetSomething() 应该是 public async Task GetSomething() 然后在你的控制器中你忘记等待一个使用 http 客户端发出请求的函数,响应永远不会到来返回。【参考方案3】:

免责声明:我不喜欢 ConfigureAwait() 解决方案,因为我发现它不直观且难以记忆。相反,我得出的结论是在 Task.Run(() => myAsyncMethodNotUsingAwait()) 中包装非等待的方法调用。这似乎 100% 有效,但可能只是竞争条件!?老实说,我不太确定发生了什么。这个结论可能是错误的,我在这里冒着 *** 的风险,希望能从 cmets 中学习 :-P。请务必阅读!

我刚刚遇到了所描述的问题,并找到了更多信息here。

声明是:“你不能调用异步方法”

await asyncmethod2()

来自阻塞的方法

myAsyncMethod().Result

就我而言,我无法更改调用方法并且它不是异步的。但我实际上并不关心结果。我记得删除 .Result 并没有等待也没有工作。

所以我这样做了:

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

就我而言,我并不关心调用非异步方法的结果,但我想这在这个用例中很常见。您可以在调用异步方法中使用结果。

【讨论】:

以上是关于在 HttpClient 中使用 await 的异步调用永远不会返回的主要内容,如果未能解决你的问题,请参考以下文章

httpclient async/await 与否

await HttpClient.PostAsync 方法编译错误[重复]

在同步类库中使用HttpClient

新的 HttpClient 代理设置问题

如何正确设置 HttpClient 的延续?

在 HttpClient 异常上显示 AlertDialog (Xamarin Android)