I'm confused about async IO operations. In this article Stephen Cleary explains that we should not use
Task.Run(() => SomeIoMethod())
standard P/Invoke asynchronous I/O system in .NET
However, avoid “fake asynchrony” in libraries. Fake asynchrony is when
a component has an async-ready API, but it’s implemented by just
wrapping the synchronous API within a thread pool thread. That is
counterproductive to scalability on ASP.NET. One prominent example of
fake asynchrony is Newtonsoft JSON.NET, an otherwise excellent
library. It’s best to not call the (fake) asynchronous versions for
serializing JSON; just call the synchronous versions instead. A
trickier example of fake asynchrony is the BCL file streams. When a
file stream is opened, it must be explicitly opened for asynchronous
access; otherwise, it will use fake asynchrony, synchronously blocking
a thread pool thread on the file reads and writes.
Does this mean that HttpClient provides not truly async operations?
HttpClient is in an unusual position, since it's primary implementation uses
HttpWebRequest, which is only partially asynchronous.
In particular, the DNS lookup is synchronous, and I think maybe the proxy resolution, too. After that, it's all asynchronous. So, for most scenarios, the DNS is fast (usually cached) and there isn't a proxy, so it acts asynchronously. Unfortunately, there are enough scenarios (particularly from within corporate networks) where the synchronous operations can cause significant lag.
So, when the team was writing
HttpClient, they had three options:
HttpWebRequest(and friends) allowing for fully-asynchronous operations. Unfortunately, this would have broken a fair amount of code. Due to the way inheritance is used as extension points in these objects, adding asynchronous methods would be backwards-incompatible.
HttpWebRequestequivalent. Unfortunately, this would take a lot of work and they'd lose all the interoperability with existing
In an ideal world (i.e., when we have infinite developer and tester time), I would prefer (2), but I understand why they chose (3).
On a side note, the code you posted shows a dangerous use of
StartNew, which has actually caused problems due to its use of
TaskScheduler.Current. This has been fixed in .NET Core - not sure when the fix will roll back into .NET Framework proper.