Skip to content

Commit 136b15e

Browse files
authored
refactor!: remove v3 binary-compatibility shims for TUnit (#372)
TUnit no longer depends on EnumerableAsyncProcessor, so the members kept solely so TUnit.Engine (compiled against v3) could bind at runtime are no longer needed: - Remove redundant ProcessInParallel(int maxConcurrency) shims from the four builder classes; int arguments still resolve to the ProcessInParallel(int?, bool) overload. - Remove the obsolete no-selector AsyncEnumerableExtensions.ProcessInParallel<T>(IAsyncEnumerable<T>, CancellationToken) collect-only overload. - Delete V3BinaryCompatibilityTests and the corresponding PublicAPI.Shipped.txt entries (v4 is not yet tagged, so the surface never shipped). - Drop the TUnit coupling notes from CLAUDE.md and the README migration guide.
1 parent b0e880c commit 136b15e

9 files changed

Lines changed: 1 addition & 115 deletions

File tree

CLAUDE.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ dotnet run --project EnumerableAsyncProcessor.UnitTests -f net10.0 -- --treenode
2929

3030
TUnit test projects compile to executables; VSTest-style `dotnet test --filter` does not work. See the `tunit-testing` skill for full filter syntax.
3131

32-
**TUnit itself depends on this library.** `TUnit.Engine` is compiled against EnumerableAsyncProcessor v3, and the locally built assembly shadows the copy TUnit shipped with (same assembly identity), so removing or changing a public member that TUnit.Engine binds to crashes test discovery with `MissingMethodException` before any test runs. The exact signatures TUnit needs are pinned by `V3BinaryCompatibilityTests` and marked as binary-compat members in the source — do not remove them until TUnit ships a build compiled against v4.
33-
3432
CI (`.github/workflows/dotnet.yml`) runs the `EnumerableAsyncProcessor.Pipeline` project (a ModularPipelines app, `dotnet run -c Release` from that directory), which builds, tests, packs, and — on `main` — publishes to NuGet. Versioning comes from GitVersion (`GitVersion.yml` pins `next-version: 4.0.0`; keep that file present, its absence makes ModularPipelines generate a Mainline config that crashes on GitHub PR merge commits).
3533

3634
## Architecture

EnumerableAsyncProcessor.UnitTests/V3BinaryCompatibilityTests.cs

Lines changed: 0 additions & 47 deletions
This file was deleted.

EnumerableAsyncProcessor/Builders/ActionAsyncProcessorBuilder.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,6 @@ public IAsyncProcessor ProcessInParallel(int? maxConcurrency = null, bool schedu
5454
return new ParallelAsyncProcessor(_count, _taskSelector, _cancellationTokenSource, maxConcurrency, scheduleOnThreadPool).StartProcessing();
5555
}
5656

57-
/// <summary>
58-
/// Processes items in parallel with bounded concurrency. Binary-compatible with assemblies
59-
/// compiled against v3 (equivalent to <c>ProcessInParallel(maxConcurrency: n)</c>).
60-
/// </summary>
61-
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
62-
public IAsyncProcessor ProcessInParallel(int maxConcurrency)
63-
{
64-
return ProcessInParallel((int?)maxConcurrency);
65-
}
66-
6757
public IAsyncProcessor ProcessOneAtATime()
6858
{
6959
return new OneAtATimeAsyncProcessor(_count, _taskSelector, _cancellationTokenSource).StartProcessing();

EnumerableAsyncProcessor/Builders/ActionAsyncProcessorBuilder_1.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,6 @@ public IAsyncProcessor<TOutput> ProcessInParallel(int? maxConcurrency = null, bo
5454
return new ResultParallelAsyncProcessor<TOutput>(_count, _taskSelector, _cancellationTokenSource, maxConcurrency, scheduleOnThreadPool).StartProcessing();
5555
}
5656

57-
/// <summary>
58-
/// Processes items in parallel with bounded concurrency. Binary-compatible with assemblies
59-
/// compiled against v3 (equivalent to <c>ProcessInParallel(maxConcurrency: n)</c>).
60-
/// </summary>
61-
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
62-
public IAsyncProcessor<TOutput> ProcessInParallel(int maxConcurrency)
63-
{
64-
return ProcessInParallel((int?)maxConcurrency);
65-
}
66-
6757
public IAsyncProcessor<TOutput> ProcessOneAtATime()
6858
{
6959
return new ResultOneAtATimeAsyncProcessor<TOutput>(_count, _taskSelector, _cancellationTokenSource).StartProcessing();

EnumerableAsyncProcessor/Builders/ItemActionAsyncProcessorBuilder_1.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,6 @@ public IAsyncProcessor ProcessInParallel(int? maxConcurrency = null, bool schedu
5757
.StartProcessing();
5858
}
5959

60-
/// <summary>
61-
/// Processes items in parallel with bounded concurrency. Binary-compatible with assemblies
62-
/// compiled against v3 (equivalent to <c>ProcessInParallel(maxConcurrency: n)</c>).
63-
/// </summary>
64-
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
65-
public IAsyncProcessor ProcessInParallel(int maxConcurrency)
66-
{
67-
return ProcessInParallel((int?)maxConcurrency);
68-
}
69-
7060
public IAsyncProcessor ProcessOneAtATime()
7161
{
7262
return new OneAtATimeAsyncProcessor<TInput>(_items, _taskSelector, _cancellationTokenSource)

EnumerableAsyncProcessor/Builders/ItemActionAsyncProcessorBuilder_2.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,6 @@ public IAsyncProcessor<TOutput> ProcessInParallel(int? maxConcurrency = null, bo
8383
return new ResultParallelAsyncProcessor<TInput, TOutput>(_items, _taskSelector, _cancellationTokenSource, maxConcurrency, scheduleOnThreadPool).StartProcessing();
8484
}
8585

86-
/// <summary>
87-
/// Processes items in parallel with bounded concurrency. Binary-compatible with assemblies
88-
/// compiled against v3 (equivalent to <c>ProcessInParallel(maxConcurrency: n)</c>).
89-
/// </summary>
90-
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
91-
public IAsyncProcessor<TOutput> ProcessInParallel(int maxConcurrency)
92-
{
93-
return ProcessInParallel((int?)maxConcurrency);
94-
}
95-
9686
/// <summary>
9787
/// Process items one at a time sequentially.
9888
/// </summary>

EnumerableAsyncProcessor/Extensions/AsyncEnumerableExtensions.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -150,26 +150,6 @@ public static async IAsyncEnumerable<TOutput> SelectManyAsync<T, TOutput>(
150150
}
151151
}
152152

153-
/// <summary>
154-
/// Collects every item from the source into a list. This overload has no work to parallelize;
155-
/// it is kept for binary compatibility with assemblies compiled against v3 (notably TUnit).
156-
/// </summary>
157-
[Obsolete("This overload only collects the stream and parallelizes nothing. Pass a selector, or enumerate the stream directly. It exists for binary compatibility with assemblies compiled against v3.")]
158-
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
159-
public static async Task<IEnumerable<T>> ProcessInParallel<T>(
160-
this IAsyncEnumerable<T> items,
161-
CancellationToken cancellationToken = default)
162-
{
163-
var results = new List<T>();
164-
165-
await foreach (var item in items.WithCancellation(cancellationToken).ConfigureAwait(false))
166-
{
167-
results.Add(item);
168-
}
169-
170-
return results;
171-
}
172-
173153
/// <summary>
174154
/// Process items in parallel with transformation and return all results as IEnumerable when awaited.
175155
/// </summary>

EnumerableAsyncProcessor/PublicAPI.Shipped.txt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder
33
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder.ActionAsyncProcessorBuilder(int count, System.Func<System.Threading.CancellationToken, System.Threading.Tasks.Task!>! taskSelector, System.Threading.CancellationToken cancellationToken) -> void
44
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder.ActionAsyncProcessorBuilder(int count, System.Func<System.Threading.Tasks.Task!>! taskSelector, System.Threading.CancellationToken cancellationToken) -> void
55
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder.ProcessInBatches(int batchSize) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
6-
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder.ProcessInParallel(int maxConcurrency) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
76
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder.ProcessInParallel(int maxConcurrency, System.TimeSpan timeSpan) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
87
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder.ProcessInParallel(int permitsPerWindow, System.TimeSpan window, int maxConcurrency) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
98
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder.ProcessInParallel(int? maxConcurrency = null, bool scheduleOnThreadPool = false) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
109
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder.ProcessOneAtATime() -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
1110
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder<TOutput>
1211
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder<TOutput>.ProcessInBatches(int batchSize) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
13-
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder<TOutput>.ProcessInParallel(int maxConcurrency) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
1412
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder<TOutput>.ProcessInParallel(int maxConcurrency, System.TimeSpan timeSpan) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
1513
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder<TOutput>.ProcessInParallel(int permitsPerWindow, System.TimeSpan window, int maxConcurrency) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
1614
EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder<TOutput>.ProcessInParallel(int? maxConcurrency = null, bool scheduleOnThreadPool = false) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
@@ -48,7 +46,6 @@ EnumerableAsyncProcessor.Builders.ExecutionCountAsyncProcessorBuilder.SelectAsyn
4846
EnumerableAsyncProcessor.Builders.ExecutionCountAsyncProcessorBuilder.SelectAsync<TOutput>(System.Func<System.Threading.Tasks.Task<TOutput>!>! taskSelector, System.Threading.CancellationToken cancellationToken) -> EnumerableAsyncProcessor.Builders.ActionAsyncProcessorBuilder<TOutput>!
4947
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput, TOutput>
5048
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput, TOutput>.ProcessInBatches(int batchSize) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
51-
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput, TOutput>.ProcessInParallel(int maxConcurrency) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
5249
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput, TOutput>.ProcessInParallel(int maxConcurrency, System.TimeSpan timeSpan) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
5350
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput, TOutput>.ProcessInParallel(int permitsPerWindow, System.TimeSpan window, int maxConcurrency) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
5451
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput, TOutput>.ProcessInParallel(int? maxConcurrency = null, bool scheduleOnThreadPool = false) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor<TOutput>!
@@ -57,7 +54,6 @@ EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput>
5754
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput>.ItemActionAsyncProcessorBuilder(System.Collections.Generic.IEnumerable<TInput>! items, System.Func<TInput, System.Threading.CancellationToken, System.Threading.Tasks.Task!>! taskSelector, System.Threading.CancellationToken cancellationToken) -> void
5855
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput>.ItemActionAsyncProcessorBuilder(System.Collections.Generic.IEnumerable<TInput>! items, System.Func<TInput, System.Threading.Tasks.Task!>! taskSelector, System.Threading.CancellationToken cancellationToken) -> void
5956
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput>.ProcessInBatches(int batchSize) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
60-
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput>.ProcessInParallel(int maxConcurrency) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
6157
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput>.ProcessInParallel(int maxConcurrency, System.TimeSpan timeSpan) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
6258
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput>.ProcessInParallel(int permitsPerWindow, System.TimeSpan window, int maxConcurrency) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
6359
EnumerableAsyncProcessor.Builders.ItemActionAsyncProcessorBuilder<TInput>.ProcessInParallel(int? maxConcurrency = null, bool scheduleOnThreadPool = false) -> EnumerableAsyncProcessor.Interfaces.IAsyncProcessor!
@@ -155,7 +151,6 @@ static EnumerableAsyncProcessor.Extensions.AsyncEnumerableExtensions.ForEachAsyn
155151
static EnumerableAsyncProcessor.Extensions.AsyncEnumerableExtensions.ProcessInParallel<T, TOutput>(this System.Collections.Generic.IAsyncEnumerable<T>! items, System.Func<T, System.Threading.Tasks.Task<TOutput>!>! taskSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<TOutput>!>!
156152
static EnumerableAsyncProcessor.Extensions.AsyncEnumerableExtensions.ProcessInParallel<T, TOutput>(this System.Collections.Generic.IAsyncEnumerable<T>! items, System.Func<T, System.Threading.Tasks.Task<TOutput>!>! taskSelector, int maxConcurrency, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<TOutput>!>!
157153
static EnumerableAsyncProcessor.Extensions.AsyncEnumerableExtensions.ProcessInParallel<T, TOutput>(this System.Collections.Generic.IAsyncEnumerable<T>! items, System.Func<T, System.Threading.Tasks.Task<TOutput>!>! taskSelector, int? maxConcurrency, bool scheduleOnThreadPool, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<TOutput>!>!
158-
static EnumerableAsyncProcessor.Extensions.AsyncEnumerableExtensions.ProcessInParallel<T>(this System.Collections.Generic.IAsyncEnumerable<T>! items, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<T>!>!
159154
static EnumerableAsyncProcessor.Extensions.AsyncEnumerableExtensions.SelectAsync<T, TOutput>(this System.Collections.Generic.IAsyncEnumerable<T>! items, System.Func<T, System.Threading.CancellationToken, System.Threading.Tasks.Task<TOutput>!>! taskSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> EnumerableAsyncProcessor.Builders.AsyncEnumerableActionAsyncProcessorBuilder<T, TOutput>!
160155
static EnumerableAsyncProcessor.Extensions.AsyncEnumerableExtensions.SelectAsync<T, TOutput>(this System.Collections.Generic.IAsyncEnumerable<T>! items, System.Func<T, System.Threading.Tasks.Task<TOutput>!>! taskSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> EnumerableAsyncProcessor.Builders.AsyncEnumerableActionAsyncProcessorBuilder<T, TOutput>!
161156
static EnumerableAsyncProcessor.Extensions.AsyncEnumerableExtensions.SelectMany<T, TOutput>(this System.Collections.Generic.IAsyncEnumerable<T>! items, System.Func<T, System.Collections.Generic.IAsyncEnumerable<TOutput>!>! selector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IAsyncEnumerable<TOutput>!

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Version 4 is a major release. Review these source and behaviour changes before u
2626

2727
- **.NET 8 is the minimum runtime.** The `net6.0` and `netstandard2.0` targets, the Polyfill dependency, and the `TaskCompletionSource` compatibility shim were removed. The package targets and tests `net8.0`, `net9.0`, and `net10.0`.
2828
- **Parallel processing has one API.** Use `ProcessInParallel(maxConcurrency: 100)` for bounded concurrency and `ProcessInParallel()` for unbounded concurrency. The old non-timed `RateLimitedParallelAsyncProcessor*` types and duplicate overload family were removed. Rename named `levelOfParallelism` arguments to `maxConcurrency` on the `ProcessInParallel` builder family (`InParallelAsync` keeps its `levelOfParallelism` parameter name); replace positional `ProcessInParallel(true)` calls with `ProcessInParallel(scheduleOnThreadPool: true)`.
29-
- **The parameterized no-selector `IAsyncEnumerable<T>.ProcessInParallel(maxConcurrency, ...)` overloads were removed.** They only buffered the stream into a list — their `maxConcurrency`/`scheduleOnThreadPool` parameters had no effect. Pass a selector (`items.ProcessInParallel(item => Task.FromResult(item), maxConcurrency)`) instead. The parameterless `ProcessInParallel(cancellationToken)` overload remains for binary compatibility with v3-compiled assemblies (notably TUnit) and is documented as a plain collect.
29+
- **The no-selector `IAsyncEnumerable<T>.ProcessInParallel(...)` overloads were removed.** They only buffered the stream into a list — their `maxConcurrency`/`scheduleOnThreadPool` parameters had no effect. Pass a selector (`items.ProcessInParallel(item => Task.FromResult(item), maxConcurrency)`) instead, or enumerate the stream directly.
3030
- **`IAsyncEnumerableProcessor` moved to `EnumerableAsyncProcessor.Interfaces`** (previously `EnumerableAsyncProcessor.Extensions`) and now implements `IAsyncDisposable`/`IDisposable`. Processors returned by the `IAsyncEnumerable<T>` builder path dispose their internal resources automatically when `ExecuteAsync` completes; they are single-use.
3131
- **Processor and builder classes are sealed.** None of them were externally subclassable in practice (their pipelines hinge on internal members); v4 makes that explicit.
3232
- **Timed processing is a real start-rate limit.** It now uses a shared token bucket instead of holding each worker slot for at least one window. `ProcessInParallel(permitsPerWindow, window, maxConcurrency)` controls start rate and in-flight concurrency independently. The existing two-argument overload remains and uses its first value for both limits.

0 commit comments

Comments
 (0)