HttpClient不提供真正的异步操作吗?

时间:2016-10-08 10:21:17

标签: c# asp.net multithreading asynchronous async-await

我对异步IO操作感到困惑。在this article Stephen Cleary解释说我们不应该使用Task.Run(() => SomeIoMethod())因为真正的异步操作应该使用

  

.NET中的标准P / Invoke异步I / O系统

http://blog.stephencleary.com/2013/11/there-is-no-thread.html

  

但是,避免库中的“假异步”。假异步就是什么时候   一个组件有一个async-ready API,但它只是通过实现   将同步API包装在线程池线程中。那是   对ASP.NET的可伸缩性起反作用。一个突出的例子   假异步是Newtonsoft JSON.NET,非常出色   图书馆。最好不要调用(假的)异步版本   序列化JSON;只需调用同步版本。一个   伪异步的一个棘手的例子是BCL文件流。当一个   文件流打开后,必须显式打开异步   访问;否则,它将使用伪异步,同步阻塞   文件上的线程池线程读写。

他建议使用HttpClient但内部使用Task.Factory.StartNew() enter image description here

这是否意味着HttpClient未提供真正的异步操作?

2 个答案:

答案 0 :(得分:4)

  

这是否意味着HttpClient不提供真正的异步操作?

排序。 HttpClient处于不寻常的位置,因为它的主要实现使用HttpWebRequest,它只是部分异步。

特别是,DNS查找是同步的,我想也许是代理解析。在那之后,它都是异步的。因此,对于大多数方案,DNS速度很快(通常是缓存的),并且没有代理,因此它以异步方式运行。不幸的是,有足够的场景(特别是来自公司网络内部),同步操作可能导致显着的延迟。

因此,当团队撰写HttpClient时,他们有三个选择:

  1. 修复HttpWebRequest(和朋友)允许完全异步操作。不幸的是,这会破坏相当数量的代码。由于继承在这些对象中用作扩展点的方式,添加异步方法将是向后兼容的。
  2. 写自己的HttpWebRequest等价物。不幸的是,这需要很多的工作,并且它们会失去与现有WebRequest相关代码的所有互操作性。
  3. 将请求排队到线程池以避免最坏情况(阻止UI线程上的同步代码)。不幸的是,这会降低ASP.NET的可伸缩性,依赖于免费的线程池线程,并且即使对于最佳情况也会产生最坏情况的成本。
  4. 理想世界中(即,当我们有无限的开发人员和测试人员时间时),我更愿意(2),但我理解为什么他们选择(3)。

    在旁注中,您发布的代码显示dangerous use of StartNew,由于使用TaskScheduler.Current 实际上caused problems。 .NET Core中的这个has been fixed - 不确定该修补程序何时会回滚到.NET Framework中。

答案 1 :(得分:0)

不,你的假设是错误的。

  1. StartNew isn't equal to the Run method.
  2. 此代码来自HttpClientHandler,而不是HttpClient,您没有检查此类中的this.startRequest代码。您正在检查的代码是一个prepare方法,它在新线程池中启动一个任务,在内部调用实际代码以启动一个http请求。
  3. HTTP连接不是在.NET抽象级别上创建的,我确信在startRequest内你找到一些P/Invoke方法,它将为:

    • DNS查询
    • 套接字连接
    • 发送请求
    • 等待答案
  4. 正如您所看到的,以上所有都是真正应该以异步方式调用的逻辑,因为它在.NET框架之外,并且某些操作可能非常耗时。这正是应该异步调用的逻辑,在等待期间,.NET线程正在ThreadPool中发布以处理其他任务。