Skip to main content

dotnet-channels-patterns

Use System.Threading.Channels for high-performance, lock-free coordination between producers and consumers. Replace legacy threading patterns with channel-based work distribution.

Source facts

Repository
visionarycoder/vc.Agentic
Last source activity
November 9, 2026 at 06:22
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
dotnet-channels-patterns
description
Use System.Threading.Channels for high-performance, lock-free coordination between producers and consumers. Replace legacy threading patterns with channel-based work distribution.
metadata
{"metadata-version":"1.0.0","title":".NET Channels Patterns","document-type":"skill","repository":"vc.Agentic","language":"Standard Technical English","updated":"2026-11-08","collection":"dotnet"}
# .NET Channels Patterns Agent uses `System.Threading.Channels` for lock-free, high-throughput producer-consumer coordination. Agent replaces legacy `BlockingCollection<T>`, `Queue<T>` with manual locks, and `Task.Run` chains with channel-based pipelines. ## When to Use | Condition | Use | |---|---| | Agent implements producer-consumer patterns between tasks or threads | Use this skill | | Agent needs high-performance work queuing without locks | Use this skill | | Agent converts BlockingCollection or manual queue synchronization | Use this skill | | Agent chains async processing stages with bounded buffers | Use this skill | ## When Not to Use | Condition | Use | |---|---| | Single-threaded async orchestration without concurrency | Use async/await patterns directly | | One-time task spawning or fire-and-forget work | Use Task.Run or hosted services | | Real-time synchronization primitive needed (Semaphore, Mutex) | Route to threading primitives | ## Required Inputs | Input | Required | Notes | |---|---|---| | Item type | Yes | Type flowing through channel | | Producer model | Yes | Single, multiple, or source-generated | | Consumer model | Yes | Single, multiple, or reader loop | | Backpressure strategy | Yes | Bounded, unbounded, or dynamic | | Completion signal | Yes | Graceful close, cancellation, or error propagation | ## Workflow Agent follows these steps: 1. Agent selects channel capacity: `CreateUnbounded<T>()` for unbounded, `CreateBounded<T>()` for backpressure. 2. Agent implements producer loop: writes items, signals completion via `Complete()`. 3. Agent implements consumer loop: reads via `ReadAllAsync()` or manual `TryRead()`. 4. Agent ensures cancellation propagates through both sides using `CancellationToken`. 5. Agent validates no deadlocks or resource leaks on exception. Test: Run `dotnet test [test-project].csproj --filter Channel` when tests exist. Pass: Zero hanging reads, zero missed completion signals, zero deadlocks. ## Common Pitfalls | Pitfall | Fix | |---|---| | Producer never calls Complete() | Call explicitly when done | | Reader never completes when channel closes | Use ReadAllAsync() which auto-completes | | Consumer loop ignores cancellation | Pass CancellationToken to ReadAllAsync() | | Unbounded channel used for all patterns | Select bounded channel for backpressure | | Exception in reader silently stops consumption | Log and rethrow or propagate via channel.Writer.TryComplete(ex) | ## Channel Selection Matrix | Use Case | Channel Type | Capacity | Buffering | |---|---|---|---| | Work queue with backpressure | Bounded | CPU count × 2 to 10× | Drop oldest or wait | | Event log or metrics stream | Unbounded | N/A | Memory-bounded by upstream | | Request batch processor | Bounded | 100–1000 | Drop or fast-fail | | Real-time pipeline stage | Bounded | 1–10 | Single-item hand-off | ## Completion and Error Handling | Scenario | Pattern | |---|---| | Producer finishes normally | Call `channel.Writer.Complete()` | | Producer encounters error | Agent completes the writer with `channel.Writer.Complete(exception)` | | Consumer detects error | Catch in read loop, rethrow or log | | Cancellation arrives mid-read | `ReadAllAsync()` cancels cleanly via token | ## Verification Matrix | Test | Run | Pass | |---|---|---| | Compile verification | `dotnet build [project].csproj` | Zero compile errors | | Completion flow | Write 10 items, close, verify reader sees all and completes | All items received, no hang | | Cancellation | Pass cancellation token to ReadAllAsync, await cancellation | Cancellation honored promptly | | Backpressure | Bound channel, high-rate producer, verify write throttling | Writer waits when buffer full | ## Verification Checklist Agent verifies: - [ ] Channel type and capacity match use case - [ ] Producer signals completion or error - [ ] Consumer loop handles all items before channel closes - [ ] Cancellation propagates cleanly - [ ] No deadlocks on exception - [ ] Disposal cleans up resources ## Legacy Pattern Migration ### Before: BlockingCollection with Manual Locking ```csharp public class LegacyWorkProcessor { private readonly BlockingCollection<WorkItem> _queue = new(100); public void Enqueue(WorkItem item) { if (!_queue.TryAdd(item, TimeSpan.FromSeconds(1))) throw new InvalidOperationException("Queue full"); } public async Task ProcessAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { if (_queue.TryTake(out var item, 100, ct)) { await ProcessItemAsync(item, ct); } } } } ``` ### After: System.Threading.Channels ```csharp public class ModernWorkProcessor( ILogger<ModernWorkProcessor> logger, IMetricsService metrics) { private Channel<WorkItem> _channel = Channel.CreateBounded<WorkItem>( new BoundedChannelOptions(100) { FullMode = BoundedChannelFullMode.Wait }); public async ValueTask EnqueueAsync(WorkItem item, CancellationToken ct) { if (!await _channel.Writer.WriteAsync(item, ct)) throw new InvalidOperationException("Channel closed"); } public async Task ProcessAsync(CancellationToken ct) { await foreach (var item in _channel.Reader.ReadAllAsync(ct)) { // exception handling example { await ProcessItemAsync(item, ct); metrics.RecordSuccess(item.Type); } catch (Exception ex) { logger.LogError(ex, "Failed to process {ItemType}", item.Type); metrics.RecordFailure(item.Type); } } } public ValueTask CompleteAsync() => _channel.Writer.WriteAsync( new WorkItem { IsTerminal = true }); private async Task ProcessItemAsync(WorkItem item, CancellationToken ct) { await Task.Delay(100, ct); } } ``` ## C# 14+ Channel Patterns ### Primary Constructor with Channel Factory ```csharp public class PipelineStage( ChannelReader<T> input, ChannelWriter<U> output, ILogger<PipelineStage> logger) { public async Task RunAsync(CancellationToken ct) { await foreach (var item in input.ReadAllAsync(ct)) { // exception handling example { var transformed = await TransformAsync(item, ct); await output.WriteAsync(transformed, ct); } catch (Exception ex) { logger.LogError(ex, "Transformation failed"); output.Writer.Complete(ex); return; } } await output.Writer.Complete(); } private async ValueTask<U> TransformAsync(T item, CancellationToken ct) { return default!; } } ``` ### File-Scoped Bounded Channel Options ```csharp file static class ChannelDefaults { internal static BoundedChannelOptions WorkQueueOptions => new(Environment.ProcessorCount * 4) { FullMode = BoundedChannelFullMode.Wait }; internal static BoundedChannelOptions FastPipelineOptions => new(1) { FullMode = BoundedChannelFullMode.DropNewest }; } ```
View on GitHub