using System.Threading.Channels; public sealed record WorkItem(CancellationToken Token) { public TaskCompletionSource Completion { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); } public sealed class AsyncGate { private readonly object sync = new(); private TaskCompletionSource source = Completed(); private static TaskCompletionSource Completed() { var value = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); value.SetResult(true); return value; } public void Close() { lock (sync) { if (source.Task.IsCompleted) source = new(TaskCreationOptions.RunContinuationsAsynchronously); } } public void Open() { lock (sync) source.TrySetResult(true); } public Task WaitAsync(CancellationToken token) { lock (sync) return source.Task.WaitAsync(token); } } public sealed class WorkLane : IDisposable { private readonly Channel queue; private readonly SemaphoreSlim pool; private readonly int workers, milliseconds; private readonly CancellationTokenSource stop = new(); private Task[] running = Array.Empty(); private int active, inPool, maxActive, maxPool, accepted, rejected, completed, canceled, disposed; public AsyncGate Gate { get; } = new(); public WorkLane(int workers, int capacity, int poolSize, int milliseconds) { this.workers = workers; this.milliseconds = milliseconds; pool = new(poolSize, poolSize); queue = Channel.CreateBounded(new BoundedChannelOptions(capacity) { FullMode = BoundedChannelFullMode.Wait, SingleWriter = false, SingleReader = workers == 1 }); } public void Start() { running = Enumerable.Range(0, workers).Select(_ => RunAsync()).ToArray(); } public bool TrySubmit(WorkItem item) { if (queue.Writer.TryWrite(item)) { Interlocked.Increment(ref accepted); return true; } Interlocked.Increment(ref rejected); return false; } private async Task RunAsync() { await foreach (WorkItem item in queue.Reader.ReadAllAsync(stop.Token)) { int now = Interlocked.Increment(ref active); Maximum(ref maxActive, now); bool entered = false; using var lifetime = CancellationTokenSource.CreateLinkedTokenSource(item.Token, stop.Token); try { lifetime.Token.ThrowIfCancellationRequested(); await Gate.WaitAsync(lifetime.Token); await pool.WaitAsync(lifetime.Token); entered = true; Maximum(ref maxPool, Interlocked.Increment(ref inPool)); await Task.Delay(milliseconds, lifetime.Token); Interlocked.Increment(ref completed); item.Completion.TrySetResult(true); } catch (OperationCanceledException) { Interlocked.Increment(ref canceled); item.Completion.TrySetCanceled(lifetime.Token); } finally { if (entered) { Interlocked.Decrement(ref inPool); pool.Release(); } Interlocked.Decrement(ref active); } } } private static void Maximum(ref int field, int value) { int old; do { old = Volatile.Read(ref field); if (old >= value) return; } while (Interlocked.CompareExchange(ref field, value, old) != old); } public object Snapshot() => new { active = Volatile.Read(ref active), queued = queue.Reader.Count, inPool = Volatile.Read(ref inPool), maxActive = Volatile.Read(ref maxActive), maxPool = Volatile.Read(ref maxPool), accepted = Volatile.Read(ref accepted), rejected = Volatile.Read(ref rejected), completed = Volatile.Read(ref completed), canceled = Volatile.Read(ref canceled) }; public async Task StopAsync(CancellationToken token) { queue.Writer.TryComplete(); try { await Task.WhenAll(running).WaitAsync(token); } catch (OperationCanceledException) when (token.IsCancellationRequested) { stop.Cancel(); Gate.Open(); try { await Task.WhenAll(running); } catch (OperationCanceledException) { } } } public void Dispose() { if (Interlocked.Exchange(ref disposed, 1) != 0) return; stop.Cancel(); pool.Dispose(); stop.Dispose(); } }