问题描述
所以我一直在通过 Reflector 研究 HttpClient.SendAsync
的实现.我特意想了解的是这些方法的执行流程,并确定调用哪个 API 来执行异步 IO 工作.
So I've been digging up on the implementation of HttpClient.SendAsync
via Reflector. What I intentionally wanted to find out was the flow of execution of these methods, and to determine which API gets called to execute the asynchronous IO work.
在探索了 HttpClient
中的各种类之后,我看到它在内部使用了 HttpClientHandler
,它派生自 HttpMessageHandler
并实现了它的 SendAsync
方法.
After exploring the various classes inside HttpClient
, I saw that internally it uses HttpClientHandler
which derives from HttpMessageHandler
and implements its SendAsync
method.
这是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
并返回 Task
由它创建.
What I found weird is that the above uses Task.Factory.StartNew
to execute the request while generating a TaskCompletionSource<HttpResponseMessage>
and returning the Task
created by it.
为什么我觉得这很奇怪?好吧,我们继续讨论 I/O 绑定异步操作如何在幕后不需要额外的线程,以及它是如何与重叠 IO 相关的.
Why do I find this weird? well, we go on alot about how I/O bound async operations have no need for extra threads behind the scenes, and how its all about overlapped IO.
为什么要使用 Task.Factory.StartNew
来触发异步 I/O 操作?这意味着 SendAsync
不仅使用纯异步控制流来执行此方法,而且在 背后" 旋转 ThreadPool 线程来执行其工作.
Why is this using Task.Factory.StartNew
to fire an async I/O operation? this means that SendAsync
isn't only using pure async control flow to execute this method, but spinning a ThreadPool thread "behind our back" to execute its work.
推荐答案
this.startRequest
是指向 StartRequest
的委托,后者又使用 HttpWebRequest.BeginGetResponse
以启动异步 IO.HttpClient
在幕后使用异步 IO,只是包裹在线程池任务中.
this.startRequest
is a delegate that points to StartRequest
which in turn uses HttpWebRequest.BeginGetResponse
to start async IO. HttpClient
is using async IO under the covers, just wrapped in a thread-pool Task.
也就是说,请注意以下 SendAsync
中的注释
That said, note the following comment in 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 工作移至线程池来避免阻塞.
This works around a well-known problem with HttpWebRequest: Some of its processing stages are synchronous. That is a flaw in that API. HttpClient
is avoiding blocking by moving that DNS work to the thread-pool.
这是好是坏?这很好,因为它使 HttpClient
成为非阻塞的并且适合在 UI 中使用.这很糟糕,因为我们现在使用线程进行长时间运行的阻塞工作,尽管我们预计根本不使用线程.这会降低使用异步 IO 的好处.
Is that good or bad? It is good because it makes HttpClient
non-blocking and suitable for use in a UI. It is bad because we are now using a thread for long-running blocking work although we expected to not use threads at all. This reduces the benefits of using async IO.
实际上,这是混合同步和异步 IO 的一个很好的例子.两者都使用并没有本质上的错误.HttpClient
和 HttpWebRequest
使用异步 IO 进行长时间运行的阻塞工作(HTTP 请求).他们将线程用于短期运行的工作(DNS,...).一般来说,这不是一个糟糕的模式.我们避免了大多数阻塞,我们只需要使一小部分代码异步.典型的 80-20 权衡.在 BCL(一个库)中找到这样的东西并不好,但在应用程序级代码中找到这样的东西是一个非常明智的权衡.
Actually, this is a nice example of mixing sync and async IO. There is nothing inherently wrong with using both. HttpClient
and HttpWebRequest
are using async IO for long-running blocking work (the HTTP request). They are using threads for short-running work (DNS, ...). That's not a bad pattern in general. We are avoiding most blocking and we only have to make a small part of the code async. A typical 80-20 trade-off. It is not good to find such things in the BCL (a library) but in application level code that can be a very smart trade-off.
似乎最好修复 HttpWebRequest
.也许出于兼容性原因,这是不可能的.
It seems it would have been preferable to fix HttpWebRequest
. Maybe that is not possible for compatibility reasons.
这篇关于HttpClient.SendAsync 使用线程池而不是异步 IO?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!