Skip to content

[API Proposal]: Task.WhenAll to return results in a strongly typed tuple #100504

Open

Description

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 Tasks because it operates on an array of Tasks of the same type.

If the Tasks 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 Tasks 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 Tasks 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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Threading.Tasksneeds-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions