Consider the pattern below, where multiple async calls are made in parallel:
var thisTask = ThisAsync();
var thatTask = ThatAsync();
await Task.WhenAll(thisTask, thatTask);
var this = await thisTask;
var that = await thatTask;
While it works fine, I find it a bit too verbose for what it is doing.
While having some fun with tuple types, I came up with the following approach that results in the same behavior:
var (this, that) = await (ThisAsync(), ThatAsync()).ResultsAsync();
It relies on an extension method on a tuple of Tasks, like below:
static class ParallelTasksExtensions
{
static async Task<(T1, T2)> ResultsAsync<T1, T2>(this (Task<T1>, Task<T2>) tasks)
{
await Task.WhenAll(tasks.Item1, tasks.Item2);
return (await tasks.Item1, await tasks.Item2);
}
}
With a couple overloads (wish C# supported variadic generic types like C++ does in templates) this can be extended to an arbitrary number of parallel calls, so that we can have something like this:
var (one, two, three, four) = await (OneAsync(), TwoAsync(), ThreeAsync(), FourAsync()).ResultsAsync();
I like it quite a bit because of the following reasons:
- Is is dramatically shorter. Usually shorter code leads to less chance of problems (not a golden rule of course, but I feel it applies here)
- It requires less state to be maintained (less variables)
- It avoids people calling
.Result
instead ofawait
on the original tasks (a common bad practice)
Another option that would result in the same behavior would be to create an extension class instead of coding this as an extension method.
For example:
var (this, that) = await TaskEx.WhenAll((ThisAsync(), ThatAsync()));
This approach would be more inline with existing, non-tuple based overloads of Task.WhenAll
.
Thoughts?
- Is it "too clever" in your opinion? I don't think there is any similar native construct that achieves this so it could be seen as clever by some I'm sure.
- Do you consider this over-engineering?
- Is it more readable/intuitive to you?
- Do you have other suggestions for the method name?
await WhenAll
? C# tasks run from the moment they're started, so they would still run in parallel even if you await them individually. \$\endgroup\$