-
-
Notifications
You must be signed in to change notification settings - Fork 5
Add polyfills for Parallel.ForAsync(...) and Parallel.ForEachAsync(...)
#63
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
Merged
Merged
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
72bc522
asdf
Tyrrrz 19a19f7
Merge branch 'master' into parallel-foreach
Tyrrrz cd7f175
Add polyfill for `Parallel.ForAsync(...)` and `Parallel.ForEachAsync(…
Tyrrrz 809502f
asd
Tyrrrz b8e82f8
Update PolyShim/Net80/Parallel.cs
Tyrrrz fa92138
Update PolyShim/Net60/Parallel.cs
Tyrrrz 40137f2
More tests
Tyrrrz 855cea7
asd
Tyrrrz 0cc1ce6
asd
Tyrrrz 6878bac
asd
Tyrrrz d085f16
Update PolyShim.Tests/Net60/ParallelTests.cs
Tyrrrz 9dab64a
asd
Tyrrrz b3458e5
asd
Tyrrrz 3dbadea
Update PolyShim/Net60/Parallel.cs
Tyrrrz b5d9fc8
Update PolyShim/Net80/Parallel.cs
Tyrrrz 7f5eb02
Update PolyShim/Net60/Parallel.cs
Tyrrrz f88eb62
Update PolyShim/Net60/Parallel.cs
Tyrrrz 2e9e513
Update PolyShim/Net60/Parallel.cs
Tyrrrz 80a7d7a
Update PolyShim/Net60/Parallel.cs
Tyrrrz 21275fa
Update PolyShim/Net60/Parallel.cs
Tyrrrz 63476e8
Update PolyShim/Net60/Parallel.cs
Tyrrrz dd0cdbc
Update PolyShim/Net60/Parallel.cs
Tyrrrz 7eb2ca2
asd
Tyrrrz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using System.Collections.Generic; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using FluentAssertions; | ||
| using Xunit; | ||
|
|
||
| namespace PolyShim.Tests.Net60; | ||
|
|
||
| public class ParallelTests | ||
| { | ||
| [Fact] | ||
| public async Task ForEachAsync_Test() | ||
| { | ||
| // Arrange | ||
| var items = new[] { 'a', 'b', 'c', 'd', 'e' }; | ||
| var results = new ConcurrentBag<char>(); | ||
|
|
||
| // Act | ||
| await Parallel.ForEachAsync( | ||
| items, | ||
| async (item, cancellationToken) => | ||
| { | ||
| await Task.Delay(10, cancellationToken); | ||
| results.Add(item); | ||
| } | ||
| ); | ||
|
|
||
| // Assert | ||
| results.Should().BeEquivalentTo(items); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ForEachAsync_Cancellation_Test() | ||
| { | ||
| // Arrange | ||
| var items = new[] { 'a', 'b', 'c', 'd', 'e' }; | ||
| var cancellationToken = new CancellationToken(true); | ||
|
|
||
| // Act & assert | ||
| var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => | ||
| await Parallel.ForEachAsync( | ||
| items, | ||
| cancellationToken, | ||
| async (_, innerCancellationToken) => await Task.Delay(10, innerCancellationToken) | ||
| ) | ||
| ); | ||
|
|
||
| ex.CancellationToken.Should().Be(cancellationToken); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ForEachAsync_MaxDegreeOfParallelism_Test() | ||
| { | ||
| // Arrange | ||
| var items = new[] { 1, 2, 3, 4, 5 }; | ||
| var currentParallelism = 0; | ||
| var maxObservedParallelism = 0; | ||
|
|
||
| // Act | ||
| await Parallel.ForEachAsync( | ||
| items, | ||
| new ParallelOptions { MaxDegreeOfParallelism = 2 }, | ||
| async (_, cancellationToken) => | ||
| { | ||
| Interlocked.Increment(ref currentParallelism); | ||
|
|
||
| int initialValue, | ||
| newValue; | ||
| do | ||
| { | ||
| initialValue = maxObservedParallelism; | ||
| newValue = Math.Max(initialValue, currentParallelism); | ||
| } while ( | ||
| Interlocked.CompareExchange(ref maxObservedParallelism, newValue, initialValue) | ||
| != initialValue | ||
| ); | ||
|
|
||
| await Task.Delay(50, cancellationToken); | ||
| Interlocked.Decrement(ref currentParallelism); | ||
| } | ||
| ); | ||
|
|
||
| // Assert | ||
| maxObservedParallelism.Should().BeLessThanOrEqualTo(2); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ForEachAsync_AsyncEnumerable_Test() | ||
| { | ||
| // Arrange | ||
| async IAsyncEnumerable<int> GetItemsAsync() | ||
| { | ||
| for (var i = 1; i <= 5; i++) | ||
| { | ||
| await Task.Delay(10); | ||
| yield return i; | ||
| } | ||
| } | ||
|
|
||
| var results = new ConcurrentBag<int>(); | ||
|
|
||
| // Act | ||
| await Parallel.ForEachAsync( | ||
| GetItemsAsync(), | ||
| async (item, cancellationToken) => | ||
| { | ||
| await Task.Delay(10, cancellationToken); | ||
| results.Add(item); | ||
| } | ||
| ); | ||
|
|
||
| // Assert | ||
| results.Should().BeEquivalentTo([1, 2, 3, 4, 5]); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| using System; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using FluentAssertions; | ||
| using Xunit; | ||
|
|
||
| namespace PolyShim.Tests.Net80; | ||
|
|
||
| public class ParallelTests | ||
| { | ||
| [Fact] | ||
| public async Task ForAsync_Test() | ||
| { | ||
| // Act | ||
| var sum = 0; | ||
| await Parallel.ForAsync( | ||
| 1, | ||
| 6, | ||
| async (i, cancellationToken) => | ||
| { | ||
| await Task.Delay(10, cancellationToken); | ||
| Interlocked.Add(ref sum, i); | ||
| } | ||
| ); | ||
|
|
||
| // Assert | ||
| sum.Should().Be(15); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ForAsync_Cancellation_Test() | ||
| { | ||
| // Arrange | ||
| var cancellationToken = new CancellationToken(true); | ||
|
|
||
| // Act & assert | ||
| var ex = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => | ||
| await Parallel.ForAsync( | ||
| 1, | ||
| 6, | ||
| cancellationToken, | ||
| async (_, innerCancellationToken) => | ||
| { | ||
| await Task.Delay(10, innerCancellationToken); | ||
| } | ||
| ) | ||
| ); | ||
|
|
||
| ex.CancellationToken.Should().Be(cancellationToken); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ForAsync_MaxDegreeOfParallelism_Test() | ||
| { | ||
| // Arrange | ||
| var currentParallelism = 0; | ||
| var maxObservedParallelism = 0; | ||
|
|
||
| // Act | ||
| await Parallel.ForAsync( | ||
| 1, | ||
| 21, | ||
| new ParallelOptions { MaxDegreeOfParallelism = 4 }, | ||
| async (_, cancellationToken) => | ||
| { | ||
| var parallelism = Interlocked.Increment(ref currentParallelism); | ||
| try | ||
| { | ||
| maxObservedParallelism = Math.Max(maxObservedParallelism, parallelism); | ||
| await Task.Delay(50, cancellationToken); | ||
| } | ||
| finally | ||
| { | ||
| Interlocked.Decrement(ref currentParallelism); | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| // Assert | ||
| maxObservedParallelism.Should().BeLessThanOrEqualTo(4); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| #if FEATURE_TASK | ||
| #if (NETCOREAPP && !NET6_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD) | ||
| #nullable enable | ||
| // ReSharper disable RedundantUsingDirective | ||
| // ReSharper disable CheckNamespace | ||
| // ReSharper disable InconsistentNaming | ||
| // ReSharper disable PartialTypeWithSinglePart | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| internal static partial class PolyfillExtensions | ||
| { | ||
| extension(Parallel) | ||
| { | ||
| // Task instead of ValueTask for maximum compatibility | ||
| // https://learn.microsoft.com/dotnet/api/system.threading.tasks.parallel.foreachasync#system-threading-tasks-parallel-foreachasync-1(system-collections-generic-ienumerable((-0))-system-threading-tasks-paralleloptions-system-func((-0-system-threading-cancellationtoken-system-threading-tasks-valuetask))) | ||
| public static async Task ForEachAsync<T>( | ||
| IEnumerable<T> source, | ||
| ParallelOptions parallelOptions, | ||
| Func<T, CancellationToken, Task> body | ||
| ) | ||
| { | ||
| using var semaphore = new SemaphoreSlim( | ||
| parallelOptions.MaxDegreeOfParallelism switch | ||
| { | ||
| > 0 => parallelOptions.MaxDegreeOfParallelism, | ||
| -1 => Environment.ProcessorCount, | ||
| _ => throw new ArgumentOutOfRangeException( | ||
| nameof(parallelOptions.MaxDegreeOfParallelism), | ||
| "Max degree of parallelism must be greater than 0 or -1 for unlimited." | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ), | ||
| } | ||
| ); | ||
|
|
||
| var tasks = source.Select(async item => | ||
| { | ||
| #if !NETFRAMEWORK || NET45_OR_GREATER | ||
| await semaphore.WaitAsync(parallelOptions.CancellationToken).ConfigureAwait(false); | ||
| #else | ||
| await Task.Run( | ||
| () => semaphore.Wait(parallelOptions.CancellationToken), | ||
| parallelOptions.CancellationToken | ||
| ).ConfigureAwait(false); | ||
| #endif | ||
|
|
||
| try | ||
| { | ||
| await body(item, parallelOptions.CancellationToken).ConfigureAwait(false); | ||
| } | ||
| finally | ||
| { | ||
| semaphore.Release(); | ||
| } | ||
| }); | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| await Task | ||
| .Factory.ContinueWhenAll( | ||
| tasks.ToArray(), | ||
| _ => { }, | ||
| parallelOptions.CancellationToken, | ||
| TaskContinuationOptions.None, | ||
| parallelOptions.TaskScheduler ?? TaskScheduler.Default | ||
| ) | ||
| .ConfigureAwait(false); | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // Task instead of ValueTask for maximum compatibility | ||
| // https://learn.microsoft.com/dotnet/api/system.threading.tasks.parallel.foreachasync#system-threading-tasks-parallel-foreachasync-1(system-collections-generic-ienumerable((-0))-system-threading-cancellationtoken-system-func((-0-system-threading-cancellationtoken-system-threading-tasks-valuetask))) | ||
| public static async Task ForEachAsync<T>( | ||
| IEnumerable<T> source, | ||
| CancellationToken cancellationToken, | ||
| Func<T, CancellationToken, Task> body | ||
| ) => | ||
| await ForEachAsync( | ||
| source, | ||
| new ParallelOptions { CancellationToken = cancellationToken }, | ||
| body | ||
| ); | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Task instead of ValueTask for maximum compatibility | ||
| // https://learn.microsoft.com/dotnet/api/system.threading.tasks.parallel.foreachasync#system-threading-tasks-parallel-foreachasync-1(system-collections-generic-ienumerable((-0))-system-func((-0-system-threading-cancellationtoken-system-threading-tasks-valuetask))) | ||
| public static async Task ForEachAsync<T>( | ||
| IEnumerable<T> source, | ||
| Func<T, CancellationToken, Task> body | ||
| ) => await ForEachAsync(source, CancellationToken.None, body); | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| #if FEATURE_ASYNCINTERFACES | ||
| // Task instead of ValueTask for maximum compatibility | ||
| // https://learn.microsoft.com/dotnet/api/system.threading.tasks.parallel.foreachasync#system-threading-tasks-parallel-foreachasync-1(system-collections-generic-iasyncenumerable((-0))-system-threading-tasks-paralleloptions-system-func((-0-system-threading-cancellationtoken-system-threading-tasks-valuetask))) | ||
| public static async Task ForEachAsync<T>( | ||
| IAsyncEnumerable<T> source, | ||
| ParallelOptions parallelOptions, | ||
| Func<T, CancellationToken, Task> body | ||
| ) | ||
| { | ||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| using var semaphore = new SemaphoreSlim( | ||
| parallelOptions.MaxDegreeOfParallelism switch | ||
| { | ||
| > 0 => parallelOptions.MaxDegreeOfParallelism, | ||
| -1 => Environment.ProcessorCount, | ||
| _ => throw new ArgumentOutOfRangeException( | ||
| nameof(parallelOptions.MaxDegreeOfParallelism), | ||
| "Max degree of parallelism must be greater than 0 or -1 for unlimited." | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ), | ||
| } | ||
| ); | ||
|
|
||
| var tasks = new List<Task>(); | ||
|
|
||
| await foreach (var item in source.WithCancellation(parallelOptions.CancellationToken)) | ||
| { | ||
| var task = Task.Factory.StartNew(async () => | ||
| { | ||
| await semaphore | ||
| .WaitAsync(parallelOptions.CancellationToken) | ||
| .ConfigureAwait(false); | ||
|
|
||
| try | ||
| { | ||
| await body(item, parallelOptions.CancellationToken).ConfigureAwait(false); | ||
| } | ||
| finally | ||
| { | ||
| semaphore.Release(); | ||
| } | ||
| }, parallelOptions.CancellationToken, TaskCreationOptions.None, parallelOptions.TaskScheduler ?? TaskScheduler.Default).Unwrap(); | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| tasks.Add(task); | ||
| } | ||
|
|
||
| await Task.WhenAll(tasks).ConfigureAwait(false); | ||
| } | ||
|
|
||
| // Task instead of ValueTask for maximum compatibility | ||
| // https://learn.microsoft.com/dotnet/api/system.threading.tasks.parallel.foreachasync#system-threading-tasks-parallel-foreachasync-1(system-collections-generic-iasyncenumerable((-0))-system-threading-cancellationtoken-system-func((-0-system-threading-cancellationtoken-system-threading-tasks-valuetask))) | ||
| public static async Task ForEachAsync<T>( | ||
| IAsyncEnumerable<T> source, | ||
| CancellationToken cancellationToken, | ||
| Func<T, CancellationToken, Task> body | ||
| ) => | ||
| await ForEachAsync( | ||
| source, | ||
| new ParallelOptions { CancellationToken = cancellationToken }, | ||
| body | ||
| ); | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Task instead of ValueTask for maximum compatibility | ||
| // https://learn.microsoft.com/dotnet/api/system.threading.tasks.parallel.foreachasync#system-threading-tasks-parallel-foreachasync-1(system-collections-generic-iasyncenumerable((-0))-system-func((-0-system-threading-cancellationtoken-system-threading-tasks-valuetask))) | ||
| public static async Task ForEachAsync<T>( | ||
| IAsyncEnumerable<T> source, | ||
| Func<T, CancellationToken, Task> body | ||
| ) => await ForEachAsync(source, CancellationToken.None, body); | ||
Tyrrrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #endif | ||
| } | ||
| } | ||
| #endif | ||
| #endif | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.