plugins/languages/csharp/skills/async/SKILL.md
C# 异步并发编程规范。涵盖 async/await、ConfigureAwait、CancellationToken 传播、 IAsyncEnumerable 异步流、System.Threading.Channels 生产者-消费者、 Parallel.ForEachAsync 并行处理、ValueTask 优化、TaskCompletionSource、 死锁与线程池饥饿排查。当编写或审查异步代码、排查 deadlock、调优并发吞吐, 或说 "async"、"await"、"Task"、"CancellationToken"、"死锁"、"thread pool starvation"、 "异步流"、"Channels"、"ValueTask" 时加载。
npx skillsauth add lazygophers/ccplugin csharp-asyncInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
异步代码默认按 ASP.NET Core / library 场景; UI 场景在末尾单列。
| 禁止 | 替代 |
|------|------|
| task.Result / task.Wait() | await task |
| Task.Run(() => syncIO) 来"异步化" | 使用真正的异步 API |
| async void 非事件处理器 | async Task |
| 漏传 CancellationToken | 全链路传递, 尊重 ct.ThrowIfCancellationRequested() |
| Thread.Sleep 在异步方法中 | await Task.Delay(ts, ct) |
| 同步上下文同步等异步 (库代码) | 全程异步 |
| _ = FireAndForgetAsync() 直接丢弃 | 包装 try/catch + 日志 |
public async Task<User> GetUserAsync(int id, CancellationToken ct = default)
{
return await _db.Users.FirstAsync(u => u.Id == id, ct);
}
Async 后缀命名CancellationToken 永远是最后一个参数, 提供 defaultTask / ValueTask; 不要返回 voidValueTask; 其余用 Taskawait x.ConfigureAwait(false), 避免捕获 SynchronizationContextfalsect 必须穿透所有异步调用。组合多源取消:
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
userCt, _appLifetime.ApplicationStopping);
cts.CancelAfter(TimeSpan.FromSeconds(30));
await DoWorkAsync(cts.Token);
var userTask = _userRepo.FindAsync(id, ct);
var ordersTask = _orderRepo.GetByUserAsync(id, ct);
await Task.WhenAll(userTask, ordersTask);
return (await userTask, await ordersTask);
数据源是流式时优先使用, 按需消费、自动取消:
public async IAsyncEnumerable<Order> StreamOrdersAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var o in _db.Orders.AsAsyncEnumerable().WithCancellation(ct))
yield return o;
}
调用方: await foreach (var o in svc.StreamOrdersAsync(ct)) { ... }。
var ch = Channel.CreateBounded<Job>(new BoundedChannelOptions(100)
{
FullMode = BoundedChannelFullMode.Wait,
SingleReader = false,
});
// Producer: await ch.Writer.WriteAsync(job, ct); 完成: ch.Writer.Complete();
// Consumer: await foreach (var j in ch.Reader.ReadAllAsync(ct)) ...
Bounded 提供背压; Unbounded 仅用于已知有界场景。
CPU/IO 混合 → Parallel.ForEachAsync 控制并发:
await Parallel.ForEachAsync(items,
new ParallelOptions { MaxDegreeOfParallelism = 8, CancellationToken = ct },
async (item, token) => await ProcessAsync(item, token));
纯 IO 限流 → Task.WhenAll + SemaphoreSlim:
using var gate = new SemaphoreSlim(10);
var tasks = urls.Select(async url =>
{
await gate.WaitAsync(ct);
try { return await _http.GetAsync(url, ct); }
finally { gate.Release(); }
});
var results = await Task.WhenAll(tasks);
ValueTaskValueTask 不能 await 多次、不能并发 awaitTaskpublic ValueTask<User?> GetUserAsync(int id, CancellationToken ct = default)
=> _cache.TryGetValue(id, out var u) ? new(u) : new(LoadAsync(id, ct));
排查思路:
dotnet-counters monitor --counters System.Runtime 看 ThreadPool Queue Lengthdotnet-stack report 找 sync-over-async 调用GetAwaiter().GetResult() / .Result? 删除ConfigureAwait(false)_ = Task.Run(async () =>
{
try { await DoBackgroundAsync(ct); }
catch (OperationCanceledException) { }
catch (Exception ex) { _logger.LogError(ex, "background failed"); }
}, ct);
async Task 测试FakeTimeProvider 测试基于时间的异步逻辑, 避免真实 Task.DelayTask.WaitAsync(timeout) 兜底async void, 内部必须 try/catchDispatcher / MainThread.BeginInvokeOnMainThreaddevelopment
Go 数据库规范——GORM Model 命名 ModelXxx、表名单数、枚举 uint8 + 常量、索引 idx_ 前缀 + deleted_at leading column、禁 time.Time 统一 int64 unix、禁指针/nullable 字段、TEXT/BLOB/JSON 禁 default、AutoMigrate 禁改主键。设计 DB model、写 GORM tag、建索引、做 migration 审查时触发。
development
Go HTTP API 规范——响应始终 200 + body code 字段、路由 /api/* 全 POST 单段 <Action><Model>、中间件逐路由注册禁 Group(prefix,mw...)、handler 仅返回 (rsp,error)、认证走 header。设计 HTTP API、写路由/handler/中间件时触发。
development
Go 项目结构规范——三层架构(API → Impl → State)、全局状态模式、internal/ 私有包、cmd/ 仅 main.go、go.work 多模块、禁止 Repository 接口和 DI 容器、struct 公共字段开头全 omitempty、handler var rsp 顶声明、禁 legacy migration。设计项目骨架、新建目录、组织包、做架构评审时触发。
development
Go 命名规范——Id/Uid 字段(非 ID)、IsActive/HasMFA 布尔前缀、CreatedAt 时间字段、接收者统一用 p、包名全小写无下划线、泛型类型参数描述性命名、集合字段 xxx_list 禁 xxxs 复数、Enum 0 值 XxxNil 禁 Unknown、禁 Status 统一 State、Set/Update 语义区分。定义结构体字段、函数、变量、包、接收者名、泛型、枚举时触发。