同步使用漂亮的 .net 4.5 HttpClient 的最佳方式

Posted

技术标签:

【中文标题】同步使用漂亮的 .net 4.5 HttpClient 的最佳方式【英文标题】:best way to use the nice .net 4.5 HttpClient synchronously 【发布时间】:2015-08-12 00:37:46 【问题描述】:

我喜欢新的 System.Net.Http.HttpClient 类。它有一个不错的简单 API,不会引发正常错误。但它只是异步的。

我需要可以运行的代码(在服务器内部)

foo();
bar();
// compute stuff
var x = GetThingFromOtherServerViaHttp();
// compute more stuff
wiz(x);

经典的顺序同步代码。我看到了几个类似的 SO 问题,但实际上从未最终说“这样做”。我看了

client.PostAsync.Wait()

全世界都在尖叫“不要这样做”。怎么样:

client.PostAsync.Result()

这不就是变相等待吗?

最后,我最终传入了一个处理结果的 lambda 回调,然后唤醒了显式等待 EventWaitHandle 的调用线程。很多管道。有没有更简单的东西,或者我应该回到使用旧的 http 客户端

编辑:进一步阅读后,我怀疑这段代码与等待和结果有相同的问题,它只是一个更冗长的死锁

编辑:我让 MS PM 最近向我确认,有一项要求“任何可能需要 > X ms(我忘了​​ X)的 API 必须是异步的”,许多 PM 将其解释为“仅异步”(不清楚是否这是预期的)。因此 Document DB api 只是异步的。

【问题讨论】:

使用Result即可,例如:***.com/a/14435574/1663001 不是。结果只是。变相等待?有什么不同。链接的 SO 有同样的问题,链接到文章说“不要这样做” 但是。我注意到HttpClient 不是.net 4.0 类。这是一个 .net 4.5 类。这意味着您可以访问异步等待。那你为什么不用它呢? @pm100:为什么不使用WebClient @ToddMenier 我在我的问题中说过,我喜欢 HttpClient 的一些功能,这是其他客户端没有提供的。特别是它不会抛出。我一直担心 MS 会开始制作仅异步的新功能,但由于许多原因,许多应用程序在任何地方都不是异步的 - 这是一种情况 【参考方案1】:

来自http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx:

return Task.Run(() => Client.PostAsync()).Result;

【讨论】:

文章实际上说不要这样做,除非你绝对必须这样做。我不确定 OP 的使用是否符合文章中描述的何时想要执行此操作的狭窄路径。 没关系,给他留下评论,但“你不想那样做”真的不是答案。我确实用最糟糕的方式回答了他的问题。 这会导致死锁吗?如果不能,我很乐意使用它。问题是,我读过直接在 HttpClient 上调用 .Result()、.Wait() 或其他东西可能会导致死锁。 不要那样做!使用任务运行异步方法只是为了阻止它有什么意义?使用var result=await Client.PostAsync(); 正确使用该方法或直接使用Client.PostAsync().Result 阻止。如果您担心等待会阻塞,例如因为 UI 被另一个操作阻塞,请使用ConfigureAwait(false),即var result=await Client.PostAsync().ConfigureAwait(false); @PanagiotisKanavos Task.Run(() => Client.PostAsync()) 几乎就是 Client.PostAsync().ConfigureAwait(false) 所做的。它确保Client.PostAsync() 的延续将被安排在线程池中,而不是在初始调用的上下文中。 Task.Run 所做的是将Client 的异步调用包装在将在线程池中执行的异步调用中。因此,Client 异步调用的继续将被安排到 ThreadPool 上下文。 ConfigureAwait 的引入几乎是为了以框架支持的方式进行相同的思考。【参考方案2】:

@Noseratio - 我当然担心死锁 :-)

您只会担心死锁如果您在具有同步上下文的线程上

这通常是 UI 应用程序的主线程(客户端),或处理 HTTP 请求的随机 ASP.NET 线程(服务器端)。无论哪种情况,您都不应该阻止它。

接受的答案可能有助于缓解死锁,但这样的阻塞仍会损害您的 UI 应用程序的最终用户体验(UI 将被冻结)或服务器端应用程序的可扩展性(阻塞浪费的线程,而它可能正在服务另一个请求)。只是不要一直阻止并使用async/await

您提到“在服务器内部”,但没有提供关于那是哪种服务器端应用程序的详细信息。大多数现代服务器端框架都为async/await 提供了良好的管道,因此接受它应该不是问题。

更新以解决评论:

我仍然不喜欢相同的代码以不同的方式编写的事实 地方会陷入僵局。我希望等待电话容易陷入僵局 投掷环境

这不是async/await 的问题,而是一般的同步上下文概念的问题,当它与阻塞代码一起使用时。简而言之,这是僵局:

private void Form1_Load(object sender, EventArgs e)

    var mre = new System.Threading.ManualResetEvent(initialState: false);
    System.Threading.SynchronizationContext.Current.Post(_ => 
        mre.Set(), null);
    mre.WaitOne();
    MessageBox.Show("We never get here");

理论上,可以尝试通过检查Thread.ThreadState == System.Threading.ThreadState.WaitSleepJoin 来缓解SynchronizationContext.Post 内部的潜在死锁。然而,这不是一个 100% 可靠的解决方案。

【讨论】:

很好的答案,但我仍然不喜欢在不同地方编写的相同代码会死锁的事实。我希望在容易死锁的环境中抛出等待调用。 我不得不说我不同意“一路异步”。编写每个服务器看起来像 node.js 没有很多优点,也有很多缺点。我希望 .net 团队不要养成编写“仅异步”的新 API 的习惯 @pm100 首先,这是一个技术问题。否则很难编写框架。但更重要的是,我不同意你的观点,即一直异步下去有缺点。相反,您应该从头到尾了解异步的可扩展性优势。这就是为什么 Java 网络服务器只能处理约 1000 个并发请求,而 node.js 中的单个线程可以用一小部分资源为数百万个请求提供服务。 @pm100,检查我的更新。至于 node.js,在地狱的回调金字塔中挣扎是很常见的(但是这个问题在 ES6 生成器和 ES7 async/await 中都将结束)。使用 C#async/await,您自然拥有非阻塞代码的伪线性、伪同步流,可以轻松地从现有同步代码转换为。例如,使用 ASP.NET,您只需使控制器方法异步,并将同步阻塞调用替换为其异步等效项 + await。即使是传统的 WebForms 也有 `RegisterAsyncTask 等。 我注意到 documentdb API 只是异步的。【参考方案3】:

有争议的是我要说System.Net.Http.HttpClient可能可以只使用.Result

微软应该遵循他们自己的建议,并在该库中一直使用.ConfigureAwait(false)。参考来源暗示了这一点:

https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs

为什么要冒险?因为它应该比Task.Run(() => Client.PostAsync()).Result更轻量级。

我不相信许多(任何?)其他库可以安全编码,并期待看到是否有人反对我的答案,或者更好地证明我错了。

【讨论】:

在某些情况下,例如,在 .NET MVC 控制器中包含 .Result 可能会导致死锁。通常如果在.Result下方有其他事情在做await 如果您从异步到同步调用链的更高层并且没有使用 .ConfigureAwait(true) 一直到 HttpClient,则正确。换句话说,您的MyAsyncHttpThing 无法避免死锁,最简单的规则仍然是“永远不要使用.Result 除非您的意思是在异步控制器中将.ResultHttpClient 混合......这很有趣,我没有考虑过这种情况。但也没有充分的理由这样做?? 我想你的意思是.ConfigureAwait(false) btw。传递 false 意味着当它恢复时,它不应该尝试在它启动的上下文上恢复(因此在控制器中,它不应该尝试在 UI 上下文上恢复。这会导致死锁) blog.stephencleary.com/2012/07/dont-block-on-async-code.html 这是一个示例,说明控制器内部的 .Result 如何导致死锁(关键是一次只能有一个线程在 UI 上下文中,并且 .Result 将等待 在 UI 上下文中,这会阻止等待的东西回到上下文中返回)

以上是关于同步使用漂亮的 .net 4.5 HttpClient 的最佳方式的主要内容,如果未能解决你的问题,请参考以下文章

如何正确理解.NET 4.5和C#5.0中的async/await异步编程模式

C#:异步编程和线程的使用(.NET 4.5 )

使用带有 .NET 4.5 HttpClient 的代理

.NET 4.5 中新提供的压缩类

我需要用于 .NET 4.5 的 Visual Studio 2012 还是 Visual Studio 2010 适合 4.5?

使用 .NET 2.0 在 XP 上运行 .NET 4.5 程序 [关闭]