使用线程池而不是异步IO的HttpClient.SendAsync?

所以我一直在通过Reflector挖掘HttpClient.SendAsync的实现。 我有意想知道的是这些方法的执行流程,以及确定调用哪个API来执行异步IO工作。

在探索了HttpClient的各种类之后,我在内部看到它使用HttpClientHandler ,它派生自HttpMessageHandler并实现其SendAsync方法。

这是HttpClientHandler.SendAsync的实现:

 protected internal override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException("request", SR.net_http_handler_norequest); } this.CheckDisposed(); this.SetOperationStarted(); TaskCompletionSource source = new TaskCompletionSource(); RequestState state = new RequestState { tcs = source, cancellationToken = cancellationToken, requestMessage = request }; try { HttpWebRequest request2 = this.CreateAndPrepareWebRequest(request); state.webRequest = request2; cancellationToken.Register(onCancel, request2); if (ExecutionContext.IsFlowSuppressed()) { IWebProxy proxy = null; if (this.useProxy) { proxy = this.proxy ?? WebRequest.DefaultWebProxy; } if ((this.UseDefaultCredentials || (this.Credentials != null)) || ((proxy != null) && (proxy.Credentials != null))) { this.SafeCaptureIdenity(state); } } Task.Factory.StartNew(this.startRequest, state); } catch (Exception exception) { this.HandleAsyncException(state, exception); } return source.Task; } 

我发现奇怪的是,上面使用Task.Factory.StartNew在生成TaskCompletionSource并返回由它创建的Task执行请求。

为什么我觉得这很奇怪? 好吧,我们继续讨论I / O绑定异步操作如何在幕后不需要额外的线程,以及它是如何重叠IO的。

为什么使用Task.Factory.StartNew来触发异步I / O操作? 这意味着SendAsync不仅使用纯异步控制流来执行此方法,而且还在“我们背后”旋转ThreadPool线程来执行其工作。

this.startRequest是一个指向this.startRequest的委托,后者又使用HttpWebRequest.BeginGetResponse来启动异步IO。 HttpClient在封面下使用异步IO,只包含在线程池任务中。

也就是说,请注意SendAsync的以下注释

 // BeginGetResponse/BeginGetRequestStream have a lot of setup work to do before becoming async // (proxy, dns, connection pooling, etc). Run these on a separate thread. // Do not provide a cancellation token; if this helper task could be canceled before starting then // nobody would complete the tcs. Task.Factory.StartNew(startRequest, state); 

这解决了HttpWebRequest的一个众所周知的问题:它的一些处理阶段是同步的。 这是该API的一个缺陷。 HttpClient通过将DNS工作移动到线程池来避免阻塞。

是好还是坏? 它很好,因为它使HttpClient非阻塞并适合在UI中使用。 这很糟糕,因为我们现在使用线程进行长时间运行的阻塞工作,尽管我们预计根本不会使用线程。 这降低了使用异步IO的好处。

实际上,这是混合同步和异步IO的一个很好的例子。 使用两者并没有任何内在错误。 HttpClientHttpWebRequest正在使用异步IO进行长时间运行的阻塞工作(HTTP请求)。 他们使用线程进行短期工作(DNS,…)。 总的来说,这不是一个糟糕的模式。 我们避免了大多数阻塞,我们只需要将代码的一小部分作为异步。 典型的80-20权衡。 在BCL(库)中找到这样的东西并不好,但在应用程序级代码中可以是一个非常聪明的权衡。

似乎最好修复HttpWebRequest 。 出于兼容性原因,这可能是不可能的。