HttpClient.SendAsync 使用线程池而不是异步 IO
本文关键字:异步 IO SendAsync 线程 HttpClient | 更新日期: 2023-09-27 18:15:10
所以我一直在挖掘通过反射器实现HttpClient.SendAsync
。我有意想要了解的是这些方法的执行流程,并确定调用哪个 API 来执行异步 IO 工作。
在探索了HttpClient
内部的各种类之后,我看到它在内部使用从HttpMessageHandler
派生的HttpClientHandler
并实现了其SendAsync
方法。
这是HttpClientHandler.SendAsync
的实现:
protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException("request", SR.net_http_handler_norequest);
}
this.CheckDisposed();
this.SetOperationStarted();
TaskCompletionSource<HttpResponseMessage> source = new TaskCompletionSource<HttpResponseMessage>();
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<HttpResponseMessage>
并返回它创建的Task
。
为什么我觉得这很奇怪? 好吧,我们继续讨论 I/O 绑定异步操作如何在幕后不需要额外的线程,以及它是如何与重叠的 IO 相关的。
为什么使用 Task.Factory.StartNew
来触发异步 I/O 操作? 这意味着SendAsync
不仅使用纯异步控制流来执行此方法,而且还在"背后">旋转 ThreadPool 线程来执行其工作。
> this.startRequest
是一个指向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的一个很好的例子。同时使用两者本身并没有什么错。 HttpClient
和HttpWebRequest
正在使用异步 IO 进行长时间运行的阻塞工作(HTTP 请求(。他们使用线程进行短期运行的工作(DNS,...(。总的来说,这不是一个糟糕的模式。我们避免了大多数阻塞,我们只需要使一小部分代码异步。典型的 80-20 权衡。在 BCL(库(中找到这样的东西并不好,但在应用程序级代码中找到这些东西可能是一个非常明智的权衡。
似乎最好修复HttpWebRequest
.出于兼容性原因,这也许是不可能的。