Initializing multiple variables with different types using parallel calls 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&lt;(T1, T2)&gt; ResultsAsync&lt;T1, T2&gt;(this (Task&lt;T1&gt;, Task&lt;T2&gt;) 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 of await 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?
I've been doing something similar to your await Task.WhenAll, though not often enough to think about a shorter way of doing it. However, I've recently come to doubt the necessity of it.

Note that there is almost zero difference in the effective behavior of this code:

var t1 = SomethingAsync();
var t2 = OtherAsync();
await Task.WhenAll(t1, t2);
var r1 = await t1;
var r2 = await t2;


and this code:

var t1 = SomethingAsync();
var t2 = OtherAsync();
var r1 = await t1;
var r2 = await t2;


In both cases, the tasks are started. In both cases, the tasks run in the background as much as possible. In both cases, the method won't finish until both tasks are done, assuming they succeed.

If only task t1 fails, both cases will throw its exception. The first version will first wait for task t2 to finish, the second version will not. This may make a difference, but usually won't.

If only task t2 fails, both cases will throw its exception, although the second version will execute a little more code first; this difference is very unlikely to make a practical difference.

If both tasks fail, both cases will throw the exception of t1 and swallow the one of t2, see here: https://blogs.msdn.microsoft.com/seteplia/2018/02/14/one-user-scenario-to-rule-them-all/ down at "Exception Handling".

So if I don't mind the swallowed exceptions, I would simply omit the WhenAll completely, which makes your utility method redundant.

That said, it would make a good place to attach code that prevents the swallowing of the exception, if that is what you want. Given that, I like your idea, but it can be improved: you can make a tuple of tasks directly awaitable. The GetAwaiter method that the compiler implicitly calls may be an extension method. That would result in this code:

static class ParallelTasksExtensions
{
    static TaskAwaiter&lt;(T1, T2)&gt; GetAwaiter&lt;T1, T2&gt;(this (Task&lt;T1&gt;, Task&lt;T2&gt;) tasks)
    {
        return AwaitAndPackage().GetAwaiter();

        async Task&lt;(T1, T2)&gt; AwaitAndPackage()
        {
            await Task.WhenAll(tasks.Item1, tasks.Item2);
            return (await tasks.Item1, await tasks.Item2);
        }
    }
}


(Warning: untested.)

Now you can just write await (t1, t2) directly. You can then proceed to overload for more arguments, of course.

Or instead you can install the TaskTupleAwaiter NuGet package, which contains exactly this code already. See also the enhancement request that led to this.