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<(T1, T2)> GetAwaiter<T1, T2>(this (Task<T1>, Task<T2>) tasks)
{
return AwaitAndPackage().GetAwaiter();
async Task<(T1, T2)> 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.