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.
After exploring the various classes inside HttpClient
, I saw that internally it uses HttpClientHandler
which derives from HttpMessageHandler
and implements its SendAsync
method.
This is the implementation of 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;
}
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.
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.
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.