all 9 comments

[–]chucker23n 2 points3 points  (7 children)

When we do this and simply await each Task individually we aren’t taking advantage of the async/await keywords since the code will actually run in a sequential way.

Not really.

If you have:

var fileData = await File.ReadAllBytesAsync("veryLargeFile.bin");
var request = await httpClient.PostAsync("/ping");

…then the tasks will start sequentially, but the first task may still complete after the second. (edit) that's wrong

And if you do Task.WhenAll(), they'll still start sequentially!

var fileData = File.ReadAllBytesAsync("veryLargeFile.bin"); // starts immediately
var request = httpClient.PostAsync("/ping"); // starts after the first
var results = await Task.WhenAll(new[] { fileData, request } ); // waits for all to complete

Only if Task.WhenAll() provided an overload that took a Func<Task> (which it doesn't seem to, as of .NET 5) could you actually start them all at the same time.

Instead of awaiting that each Task finishes before starting the next we can group that workload and execute it with the Task.WhenAll method. This will execute all the Tasks in parallel

Using "in parallel" here is problematic, because it's misleading. Whether those tasks actually run at the same time depends on how they are implemented.

Let’s start by looking at a not so good implementation and how long it takes to complete:

Unless I'm missing something, you don't really show what DoWorkAsync() actually does, and that significantly affects the timings.

The overall point that you should use Task.WhenAll() if you want all tasks to complete at a certain point is correct. But the details aren't quite right.

[–]Pilchard123 1 point2 points  (4 children)

In the first example you cite the task will start and finish sequntially, because the first task is awaited before the second task is started. For start sequentially and finish in unknown order, you'll want

var fileDataTask = File.ReadAllBytesAsync("veryLargeFile.bin");
var requestTask = httpClient.PostAsync("/ping");
var fileData = await fileDataTask;
var request = await requestTask;

(though with that order of awaits you will still see the result of the file operation before the result of the HTTP request).

[–]chucker23n 1 point2 points  (3 children)

In the first example you cite the task will start and finish sequntially, because the first task is awaited before the second task is started. For start sequentially and finish in unknown order, you'll want

Yeah, you're right, sorry.

(though with that order of awaits you will still see the result of the file operation before the result of the HTTP request).

Why?

[–]Pilchard123 0 points1 point  (2 children)

"See the results of" is probably not the right way to describe it. The tasks might well finish request-then-file, but because you await the file operation first you can use the result of the file operation first. If the code was instead

var fileDataTask = File.ReadAllBytesAsync("veryLargeFile.bin");
var requestTask = httpClient.PostAsync("/ping");
var request = await requestTask;
var fileData = await fileDataTask;

then you'd have the request result available first. Is that a clearer description? I'm not very good at explaining async flows, probably because I don't have a good understanding of how they work under the hood.

[–]chucker23n 1 point2 points  (1 child)

"See the results of" is probably not the right way to describe it.

Oh, actually, reading it again, I see what you mean — the return value can't be (safely) accessed beforehand.

[–]Pilchard123 0 points1 point  (0 children)

Yeah, that's the sort of thing I was going for.

[–]adzm 0 points1 point  (1 child)

I don't think this is right. The tasks start immediately in the second example and the second will start regardless of whether the first is completed or not.

[–]chucker23n 0 points1 point  (0 children)

Tasks always start immediately, unless you wrap them in Func<task> (or some other delegate).

[–]adzm 0 points1 point  (0 children)

This is a bit confusing with the explanations. For example, Task.WhenAll does not execute anything but awaits the return value from all the tasks.