It would probably be cleaner to not use Task.WhenAll() but just await the results individually. This is because there’s no reason to wait for locationTask to be completed prior to assigning contacts to ViewBag. Additionally, I do not think it is a good idea to suggest that people use Task.Result. It is rarely necessary and can introduce blocking if you haven’t verified that the task has run to completion. For example:
var contactsTask = Client.DownloadStringTaskAsync("api/contacts");
var temperatureTask = Client.DownloadStringTaskAsync("api/temperature");
var locationTask = Client.DownloadStringTaskAsync("api/location");
ViewBag.contacts = await contactsTask;
ViewBag.temperature = await temperatureTask;
ViewBag.locationTask = await locationTask;
I’d also like to note that most methods that support Task variants are named SynchronousNameAsync() instead of SynchronousNameTaskAsync().
Additionally, it is misleading to say that all sockets/file IO operations support async. In fact, those APIs are largely missing it. For example, there is no way to open a file asynchronously or close a socket or NetworkStream asynchronously. For most cases in Windows, one can use FlushAsync() on a stream prior to calling Close(), but it’s quite difficult to write such code without accidentally introducing blocking.