Would those 3 examples behave identically
2nd and 3rd will not differ in behaviour (except for the 3rd generating redundant async state machine, compiler can even suggest to remove async-await
here - @sharplab.io)
As for the 1st vs 2nd/3rd - it depends on actual implementation of GetUsersAsync
, GetCarsAsync
, if they are not truly async or contain significant CPU-bound portion before first await then that portion would be handled by the same thread which executes the calling method. I.e:
var startNew = Stopwatch.StartNew();
var task1 = GetUsersAsync();
var task2 = GetCarsAsync();
await Task.WhenAll(task1,task2);
Console.WriteLine(startNew.ElapsedMilliseconds); // Prints ~ 2100
async Task GetUsersAsync()
{
Thread.Sleep(1000); // simulate CPU-bound work, note that changing order will change the behavior
await Task.Delay(100);
}
async Task GetCarsAsync()
{
Thread.Sleep(1000);
await Task.Delay(100);
}
While Task.Run
approach will schedule the task on thread pool and return immediately:
var startNew = Stopwatch.StartNew();
var task1 = Task.Run(GetUsersAsync);
var task2 = Task.Run(GetCarsAsync);
await Task.WhenAll(task1,task2);
Console.WriteLine(startNew.ElapsedMilliseconds); // Prints ~ 1100
P.S.
Not very relevant in this particular case, but still very useful for general understanding article - Eliding Async and Await by Stephen Cleary.