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.
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);
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.
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.
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.
It seems it would have been preferable to fix HttpWebRequest
. Maybe that is not possible for compatibility reasons.