Description
openedon Apr 2, 2024
Background and motivation
A very common scenario is to run multiple asynchronous operations in parallel and continue once all of them returned their results. Currently, the only API we have for such scenarios is the Task.WhenAll
, but it loses the type information of the individual Task
s because it operates on an array of Task
s of the same type.
If the Task
s are of different types, await-ing them in parallel and capturing their results becomes cumbersome.
With the ability to deconstruct tuples at assignment, there could be an API to run multiple Task
s of different types in parallel and return their results as a tuple which can be deconstructed immediately without having to hold on to intermediate Task<T>
references.
API Proposal
namespace System.Threading.Tasks;
class Task
{
public static Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
{
// note: I'm pretty sure there's a more efficient implementation than this
return Task.WhenAll(task1, task2).ContinueWith(_ => (task1.Result, task2.Result));
}
public static Task<(T1, T2, T3)> WhenAll<T1, T2, T3>(Task<T1> task1, Task<T2> task2, Task<T3> task3);
public static Task<(T1, T2, T3, T4)> WhenAll<T1, T2, T3, T4>(Task<T1> task1, Task<T2> task2, Task<T3> task3, Task<T4> task4);
public static Task<(T1, T2, T3, T4, T5)> WhenAll<T1, T2, T3, T4, T5>(Task<T1> task1, Task<T2> task2, Task<T3> task3, Task<T4> task4, Task<T5> task5);
}
API Usage
async Task<string> GetStringAsync() => "Hello World";
async Task<int> GetIntAsync() => 42;
var (s, i) = await Task.WhenAll(
GetStringAsync(),
GetIntAsync());
Alternative Designs
I know that this wouldn't quite work because the Task.WhenAll
API already has params
overload, so there would be ambiguity or unwanted preference between the overloads, but maybe the provided Task<T>
parameters could be wrapped in a ValueTuple
to avoid that overload collision, which would look something like this:
public static Task<(T1, T2)> WhenAll<T1, T2>((Task<T1>, Task<T2>) tasks);
I'll admit, this does look a little weird from the caller's perspective though.
Another alternative is to use a different method name, such as WhenEach
(though it rather rhymes with foreach
and is a new API planned for .NET 9 ( #61959 ) returning Task
s via an IAsyncEnumerable
as they complete, so returning a Tuple
/ ValueTuple
there might be confusing, plus, I'd assume it will also have a params
overload and would meet with the same problem as the WhenAll
API).
So, WhenEvery
, WhenSome
, WhenAllIndividual
?