所以我一直在通过Reflector挖掘HttpClient.SendAsync
的实现。我有意想知道的是这些方法的执行流程,以及确定调用哪个API来执行异步IO工作。
在浏览HttpClient
中的各个类之后,我在内部看到它使用HttpClientHandler
,它派生自HttpMessageHandler
并实现其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
不仅仅使用纯异步控制流来执行此方法,而是在我们的后面&#34; 后面旋转一个ThreadPool线程&#34来执行其工作
答案 0 :(得分:13)
this.startRequest
是一个指向StartRequest
的委托,后者又使用HttpWebRequest.BeginGetResponse
启动异步IO。 HttpClient
正在使用异步IO,只包含在线程池任务中。
尽管如此,请注意以下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);
This works around a well-known problem with HttpWebRequest: Some of its processing stages are synchronous.这是该API的一个缺陷。 HttpClient
通过将DNS工作移动到线程池来避免阻塞。
是好还是坏?这很好,因为它使HttpClient
非阻塞并且适合在UI中使用。这很糟糕,因为我们现在使用一个线程进行长时间运行的阻塞工作,尽管我们预计根本不会使用线程。这降低了使用异步IO的好处。
实际上,这是混合同步和异步IO的一个很好的例子。使用两者并没有任何内在错误。 HttpClient
和HttpWebRequest
正在使用异步IO进行长时间运行的阻止工作(HTTP请求)。他们使用线程进行短期工作(DNS,...)。总的来说,这不是一个糟糕的模式。我们避免大多数阻塞,我们只需要将代码的一小部分作为异步。典型的80-20权衡。在BCL(一个库)中找到这样的东西并不好,但是在应用程序级代码中可以进行非常明智的权衡。
似乎最好修复HttpWebRequest
。出于兼容性原因,这可能是不可能的。