Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: LINQ terminator .WhenAll() wrapping Task.WhenAll #107706

Closed
Regenhardt opened this issue Sep 11, 2024 · 11 comments
Closed

[API Proposal]: LINQ terminator .WhenAll() wrapping Task.WhenAll #107706

Regenhardt opened this issue Sep 11, 2024 · 11 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Threading.Tasks

Comments

@Regenhardt
Copy link
Contributor

Background and motivation

This would support the fluent API of LINQ and avoid having to wrap a LINQ stack or its return value in a Task.WhenAll() call.

API Proposal

namespace System.LINQ;

public static class IEnumerableExtensions
{
    public static async Task WhenAll(this IEnumerable<Task> tasks)
    {
        return await Task.WhenAll(tasks);
    }

    public static async Task<T[]> WhenAll(this IEnumerable<Task<T>> tasks)
    {
        return await Task.WhenAll(tasks);
    }
}

API Usage

public async Task SpreadTheWordAsync(Person caller, string message, string label)
    {
        await allThePeople
            .Where(person => person.Labels.Contains(label))
            .Where(person => person != caller)
            .Select(person => MessagePersonAsync(person, message))
            .WhenAll();

        await MessagePersonAsync(caller, "The word has been spread");
    }

Alternative Designs

The await keyword could accept an IEnumerable instead.

Risks

People might have already built this on their own.

@Regenhardt Regenhardt added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Sep 11, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Sep 11, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-linq
See info in area-owners.md if you want to be subscribed.

@huoyaoyuan
Copy link
Member

This doesn't really belong to LINQ. LINQ provides methods for collections of every type, not a particular type.

The proposal is about creating simple wrappers of existing methods to satisfy certain language feature. Generally we won't do this as it's very simple for users to create their own. In other languages like F#, you can use |> Task.WhenAll without a wrapper.

Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-threading-tasks
See info in area-owners.md if you want to be subscribed.

@eiriktsarpalis
Copy link
Member

There is precedent in us retroactively marking static methods as extension methods as long as their signature permits it. We could conceivably do that with the Task.WhenAll methods, but I'm not sure if this pattern is common enough for such a change to be warranted.

@stephentoub @terrajobst thoughts?

@Joe4evr
Copy link
Contributor

Joe4evr commented Sep 12, 2024

There is precedent in us retroactively marking static methods as extension methods as long as their signature permits it.

Aren't extension methods only allowed inside static classes? Task is not a static type, so it wouldn't be allowed to change the existing method.

@MihaZupan
Copy link
Member

FWIW this functionality already exists in System.Linq.Async

await allThePeople
+   .ToAsyncEnumerable()
    .Where(person => person.Labels.Contains(label))
    .Where(person => person != caller)
-   .Select(person => MessagePersonAsync(person, message))
-   .WhenAll();
+   .ForEachAwaitAsync(person => MessagePersonAsync(person, message));

@huoyaoyuan
Copy link
Member

There is precedent in us retroactively marking static methods as extension methods as long as their signature permits it. We could conceivably do that with the Task.WhenAll methods

Note: this currently isn't allowed by C# language, which requires extension methods be defined in static types.

@eiriktsarpalis
Copy link
Member

Aren't extension methods only allowed inside static classes?

Ah yes, good point. I don't think this change would be viable today.

@terrajobst
Copy link
Member

My gut reaction is that it feels niche and doesn't really add much syntactic sugar over the "ordinary way":

public async Task SpreadTheWordAsync(Person caller, string message, string label)
{
    await Task.WhenAll(
        allThePeople
        .Where(person => person.Labels.Contains(label))
        .Where(person => person != caller)
        .Select(person => MessagePersonAsync(person, message))
    );

    await MessagePersonAsync(caller, "The word has been spread");
}

Now, this would be a lot more compelling if we extended Linq-style operators for IAsyncEnumerable<T>, like so

public async Task SpreadTheWordAsync(Person caller, string message, string label)
{
    await allThePeople
        .Where(person => person.Labels.Contains(label))
        .Where(person => person != caller)
        .Select(person => MessagePersonAsync(person, message))
        .ToAsyncEnumerable()
        .WhenAll();

    await MessagePersonAsync(caller, "The word has been spread");
}

@eiriktsarpalis
Copy link
Member

Agreed. I suppose we can close this in favor of #79782.

@huoyaoyuan
Copy link
Member

The await keyword could accept an IEnumerable instead.

Note that this is already achievable today:

    public static TaskAwaiter GetAwaiter(this IEnumerable<Task> tasks) => Task.WhenAll(tasks).GetAwaiter();
    public static TaskAwaiter<T[]> GetAwaiter<T>(this IEnumerable<Task<T>> tasks) => Task.WhenAll(tasks).GetAwaiter();

With extension GetAwaiter you can make everything awaitable.

@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Sep 13, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Oct 13, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Threading.Tasks
Projects
None yet
Development

No branches or pull requests

6 participants