Parallel task runner that preserves per-key ordering while keeping lane-independent work highly parallel.
$ dotnet add package ParallelLanesParallelLanes is a lightweight lane-aware scheduler for .NET. It keeps unrelated work highly parallel while guaranteeing that items sharing the same key never overlap. Think of it as a parallel runner with automatic per-key mutexes.
IServiceCollection.Once the package is published:
dotnet add package ParallelLanes
Until then, clone the repo and reference src/ParallelLanes/ParallelLanes.csproj directly.
using ParallelLanes;
await using var runner = new ParallelLaneRunner(new ParallelLaneRunnerOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount
});
var work = new List<Task>
{
runner.EnqueueAsync("alpha", async ct =>
{
await Task.Delay(150, ct);
}),
runner.EnqueueAsync("beta", async ct =>
{
await Task.Delay(150, ct);
}),
runner.EnqueueAsync("alpha", async ct =>
{
// Runs only after the first "alpha" item finishes.
await Task.Delay(150, ct);
})
};
await Task.WhenAll(work);
var options = new ParallelLaneRunnerOptions
{
MaxDegreeOfParallelism = 4,
LaneKeyComparer = StringComparer.OrdinalIgnoreCase
};
MaxDegreeOfParallelism: upper bound on concurrent workers (default: logical processor count).LaneKeyComparer: comparer used to group lane keys (default: StringComparer.Ordinal).using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ParallelLanes;
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddSimpleConsole());
services.AddParallelLaneRunner(options =>
{
options.MaxDegreeOfParallelism = Math.Max(2, Environment.ProcessorCount / 2);
});
await using var provider = services.BuildServiceProvider();
await using var runner = provider.GetRequiredService<IParallelLaneRunner>();
The extension registers IParallelLaneRunner as a singleton and applies any custom options you supply.
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var task = runner.EnqueueAsync("io", async ct =>
{
await DoWorkAsync(ct);
}, cts.Token);
// DisposeAsync cancels queued work and waits for workers to shut down.
await runner.DisposeAsync();
EnqueueAsync returns a task that completes when the job finishes or propagates the failure.Measured on an Apple M2 Pro (BenchmarkDotNet 0.14.0):
| Scenario | 32 items | 128 items |
|---|---|---|
| Sequential (single lane) | 73.40 ms | 293.05 ms |
| Parallel lanes (single key) | 74.39 ms | 295.36 ms |
| Parallel lanes (mixed keys) | 18.86 ms | 74.38 ms |
dotnet run -c Release --project tests/ParallelLanes.Benchmarks/ParallelLanes.Benchmarks.csproj -- --filter "*".src/ParallelLanes – main library.samples/ParallelLanes.SampleApp – console demo showcasing DI and logging.tests/ParallelLanes.Tests – correctness suite with xUnit.tests/ParallelLanes.Benchmarks – BenchmarkDotNet scenarios.Issues and pull requests are welcome. Please open an issue describing proposed changes before submitting large PRs.
This project is licensed under the MIT License. See LICENSE for details.