用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/rudironsoni/Synaxis --skill dotnet-channels命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Routes .NET/C# work to domain skills. Loads coding-standards for code paths.
基于 SOC 职业分类
正在显示 SKILL.md
| name | dotnet-channels |
| category | fundamentals |
| subcategory | coding-standards |
| description | Implements producer/consumer queues. Channel<T>, bounded/unbounded, backpressure, drain. |
| license | MIT |
| targets | ["*"] |
| tags | ["foundation","dotnet","skill"] |
| version | 0.0.1 |
| author | dotnet-agent-harness |
| invocable | true |
| claudecode | {"allowed-tools":["Read","Grep","Glob","Bash","Write","Edit"]} |
| codexcli | {"short-description":".NET skill guidance for foundation tasks"} |
| opencode | {"allowed-tools":["Read","Grep","Glob","Bash","Write","Edit"]} |
| copilot | {} |
| geminicli | {} |
| antigravity | {} |
Deep guide to System.Threading.Channels for high-performance, thread-safe producer/consumer communication in .NET.
Covers channel creation, backpressure strategies, IAsyncEnumerable integration, and graceful shutdown patterns.
Cross-references: [skill:dotnet-background-services] for integrating channels with hosted services, [skill:dotnet-csharp-async-patterns] for async patterns used in channel consumers.
A Channel<T> is a thread-safe data structure with separate ChannelWriter<T> and ChannelReader<T> endpoints.
Writers produce items, readers consume them -- the channel handles all synchronization.
// Create a channel and separate the endpoints
Channel<WorkItem> channel = Channel.CreateUnbounded<WorkItem>();
ChannelWriter<WorkItem> writer = channel.Writer;
ChannelReader<WorkItem> reader = channel.Reader;
```text
### Bounded vs Unbounded
| Aspect | Bounded | Unbounded |
| ------------- | ---------------------------------------------- | ---------------------------------- |
| Creation | `Channel.CreateBounded<T>(capacity)` | `Channel.CreateUnbounded<T>()` |
| Back-pressure | Yes -- `FullMode` controls behavior when full | No -- grows without limit |
| Memory safety | Capped at `capacity` items | Can exhaust memory under load |
| Use when | Production workloads, untrusted producer rates | Guaranteed-low-volume, prototyping |
```csharp
// Bounded -- preferred for production
var bounded = Channel.CreateBounded<WorkItem>(new BoundedChannelOptions(capacity: 1000)
{
FullMode = BoundedChannelFullMode.Wait
});
// Unbounded -- use only when you control the producer rate
var unbounded = Channel.CreateUnbounded<WorkItem>();
```text
---
Controls what happens a bounded channel full a producer attempts to write.
| Mode | Behavior | Use |
| ------------ | ---------------------------------------------------------------- | ---------------------------------------------------- |
| `Wait` | `WriteAsync` blocks until space available | Default. Reliable delivery back-pressure |
| `DropOldest` | Drops the oldest item the channel to make room | Telemetry, metrics -- latest data matters most |
| `DropNewest` | = Channel.CreateBounded<SensorReading>( BoundedChannelOptions()
{
FullMode = BoundedChannelFullMode.DropOldest
});
logChannel = Channel.CreateBounded<LogEntry>( BoundedChannelOptions(_000)
{
FullMode = BoundedChannelFullMode.DropWrite
});
(!logChannel.Writer.TryWrite(entry))
{
overflowCounter.Add();
}
```text
Starting .NET , bounded channels drop modes accept an `itemDropped` callback that fires whenever an item
discarded. Use metrics, logging, resource cleanup dropped items.
```csharp
channel = Channel.CreateBounded( BoundedChannelOptions()
{
FullMode = BoundedChannelFullMode.DropOldest
},
itemDropped: (item, writer) =>
{
logger.LogWarning(, item.Id);
droppedItemsCounter.Add();
(item IDisposable)?.Dispose();
});
```text
The callback receives the dropped item the `ChannelWriter<T>` (useful you need to re-route items to a fallback
channel).
---
```csharp
writer.WriteAsync(item, cancellationToken);
(!writer.TryWrite(item))
{
}
```text
Multiple producers can call `WriteAsync` `TryWrite` concurrently without external locking. The channel internally
thread-safe.
```csharp
app.MapPost(, (
id,
ChannelWriter<OrderCommand> writer,
CancellationToken ct) =>
{
writer.WriteAsync( OrderCommand(id, ), ct);
Results.Accepted();
});
app.MapPost(, (
id,
ChannelWriter<OrderCommand> writer,
CancellationToken ct) =>
{
writer.WriteAsync( OrderCommand(id, ), ct);
Results.Accepted();
});
```bash
Call `Complete()` `TryComplete()` no more items will be produced. This lets consumers detect the end of the
stream.
```csharp
writer.Complete();
writer.TryComplete();
writer.TryComplete( InvalidOperationException());
```text
---
The classic pattern: wait an item, process it, repeat.
```
{
(reader.TryRead( item))
{
ProcessAsync(item, cancellationToken);
}
}
```text
This two-loop pattern preferred over `ReadAsync` alone because it drains all available items before awaiting again,
reducing state machine overhead.
For simpler cases per-item overhead acceptable:
```csharp
{
()
{
item = reader.ReadAsync(cancellationToken);
ProcessAsync(item, cancellationToken);
}
}
(ChannelClosedException)
{
}
```text
Scale processing running multiple consumer tasks. The channel ensures each item read exactly one consumer.
```
{
WorkerCount = ;
{
workers = Enumerable.Range(, WorkerCount)
.Select(i => ConsumeAsync(i, stoppingToken));
Task.WhenAll(workers);
}
{
logger.LogDebug(, workerId);
( reader.WaitToReadAsync(ct))
{
(reader.TryRead( item))
{
{
scope = scopeFactory.CreateScope();
handler = scope.ServiceProvider
.GetRequiredService<IWorkItemHandler>();
handler.HandleAsync(item, ct);
}
(Exception ex)
{
logger.LogError(ex,
,
workerId, item.Id);
}
}
}
logger.LogDebug(, workerId);
}
}
```text
---
`ChannelReader<T>.ReadAllAsync()` returns an `IAsyncEnumerable<T>`, enabling ` ` consumption integration
LINQ operators.
```
{
ProcessAsync(item, cancellationToken);
}
```text
`ReadAllAsync` the simplest consumption pattern. It handles `WaitToReadAsync`/`TryRead` internally completes
the channel closed.
Channels combine naturally ASP.NET Core streaming responses. Return the `IAsyncEnumerable<T>` directly -- minimal
APIs will stream items JSON array elements:
```csharp
app.MapGet(, (
ChannelReader<ServerEvent> reader,
CancellationToken ct) => reader.ReadAllAsync(ct));
```csharp
With the `System.Linq.Async` NuGet package, channel streams compose familiar LINQ operators:
```csharp
( batch reader.ReadAllAsync(ct)
.Where(item => item.Priority >= Priority.High)
.Buffer()
.WithCancellation(ct))
{
BulkProcessAsync(batch, ct);
}
```text
```
{
channel = Channel.CreateUnbounded<PriceUpdate>();
_ = Task.Run( () =>
{
{
( tick marketFeed.SubscribeAsync(symbol, ct))
{
channel.Writer.WriteAsync(tick, ct);
}
channel.Writer.TryComplete();
}
(Exception ex)
{
channel.Writer.TryComplete(ex);
}
}, ct);
( update channel.Reader.ReadAllAsync(ct))
{
update;
}
}
```text
---
Setting `SingleReader = ` `SingleWriter = ` channel options enables -free optimizations. The channel
trusts these hints -- = Channel.CreateBounded<T>( BoundedChannelOptions()
{
SingleReader = ,
SingleWriter = ,
FullMode = BoundedChannelFullMode.Wait
});
```text
The most efficient consumer pattern. `WaitToReadAsync` suspends until data available, then `TryRead` drains all
buffered items synchronously -- avoiding per-item state machine overhead.
```
{
(reader.TryRead( item))
{
Process(item);
}
}
```text
`TryWrite` synchronous allocation-free the channel has space. Prefer it over `WriteAsync` hot paths
you can handle the `` .
```csharp
(!writer.TryWrite(item))
{
writer.WriteAsync(item, ct);
}
```text
Bounded channels pre-allocate an array of `capacity` slots.
{
( item reader.ReadAllAsync(stoppingToken))
{
ProcessAsync(item, stoppingToken);
}
}
(OperationCanceledException) (stoppingToken.IsCancellationRequested)
{
}
```text
Complete the writer to signal no more items will arrive, then drain remaining items before stopping. This prevents data
loss during shutdown.
```
{
{
reader = channel.Reader;
{
( reader.WaitToReadAsync(stoppingToken))
{
(reader.TryRead( item))
{
scope = scopeFactory.CreateScope();
handler = scope.ServiceProvider
.GetRequiredService<IWorkItemHandler>();
handler.HandleAsync(item, stoppingToken);
}
}
}
(OperationCanceledException) (stoppingToken.IsCancellationRequested)
{
}
channel.Writer.TryComplete();
logger.LogInformation();
drainCts = CancellationTokenSource(TimeSpan.FromSeconds());
(reader.TryRead( remaining))
{
{
scope = scopeFactory.CreateScope();
handler = scope.ServiceProvider
.GetRequiredService<IWorkItemHandler>();
handler.HandleAsync(remaining, drainCts.Token);
}
(Exception ex)
{
logger.LogWarning(ex, );
}
}
logger.LogInformation();
}
}
```text
The host shutdown timeout seconds. If your drain needs more time, configure it:
```csharp
builder.Services.Configure<HostOptions>(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds();
});
```text
---
**Do use unbounded channels production without rate control** -- they can exhaust memory under sustained
producer pressure. Always prefer bounded channels capacity.
**Do violate SingleReader/SingleWriter promises** -- these flags enable -free optimizations. Multiple
concurrent readers `SingleReader = ` causes data corruption, exceptions.
**Do forget to call `Complete()` the writer** -- without completion, consumers `ReadAllAsync()`
`WaitToReadAsync` will wait indefinitely after the last item.
**Do `ChannelClosedException` globally** -- it signals that the writer called `Complete()`, possibly
an error. Catch it only around `ReadAsync` calls; `WaitToReadAsync`/`TryRead` loops handle completion via ``
.
**Do use `ReadAsync` hot paths** -- prefer the `WaitToReadAsync` + `TryRead` pattern to drain buffered items
synchronously reduce state machine allocations.
**Do block the `itemDropped` callback** -- it runs synchronously the writer