dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.36k stars 4.75k forks source link

Parallel.ForEachAsync with thread-local state just like Parallel.ForEach #89476

Open douglasg14b opened 1 year ago

douglasg14b commented 1 year ago

How can we use thread-local state with Parallel.ForEachAsync? Is this just not a feature that exists, or are we expected to do something special to make this work?

Parallel.ForEach example definition:

        public static ParallelLoopResult ForEach<TSource, TLocal>(
            Partitioner<TSource> source,
            Func<TLocal> localInit,
            Func<TSource, ParallelLoopState, TLocal, TLocal> body,
            Action<TLocal> localFinally)
        {

Working with .Net 7.

Clockwork-Muse commented 1 year ago

How can we use thread-local state with Parallel.ForEachAsync?

Do you want actual per-thread data, or a (perhaps more nebulous) per-runner data? There's a difference.

douglasg14b commented 1 year ago

Per thread data, for instance if I am using DI and need to generate a scope for each thread, not necessarily per item.

Perhaps I misunderstand the difference. Can you explain per-runner vs per-thread?

Clockwork-Muse commented 1 year ago

async tasks can (and often do) move between runtime/OS threads. So for your DI scenario, individual items might be moved between different scopes, which might not be what you expect.

A per-runner context would sort-of look like per-thread context, in that it would cover multiple items (almost certainly based on the partition, just as for threads), but it would follow the tasks over any suspension points, which is possibly less surprising behavior.

ghost commented 1 year ago

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

Issue Details
How can we use thread-local state with `Parallel.ForEachAsync`? Is this just not a feature that exists, or are we expected to do something special to make this work? `Parallel.ForEach` example definition: ```cs public static ParallelLoopResult ForEach( Partitioner source, Func localInit, Func body, Action localFinally) { ``` Working with .Net 7.
Author: douglasg14b
Assignees: -
Labels: `area-System.Linq.Parallel`, `untriaged`
Milestone: -
douglasg14b commented 1 year ago

Gotcha, so yeah, I'm talking about per-runner state.

Similar to if I created a bunch of these in a loop with some fragment of data from the enclosing scope:

Task.Run(async () =>
{
    var services = _scopeFactory.CreateScope().ServiceProvider; // _scopeFactory in outer scope
    var myService= services.GetRequiredService<MyService>();

    await myService.DoIoBoundStuff();
});
PranavSenthilnathan commented 4 days ago

Sounds like a reasonable proposal. Similar to the case for Parallel.ForEach, having a partition-local variable avoids having to create a closure and/or hand-rolling state management in ways that could lead to bad perf from locking or false sharing.

@douglasg14b could you update your post to follow the template here?