

新闻资讯
技术学院Stephen Cleary 强调异步编程的核心是避免错误:禁用 async void(仅限事件处理器),库中默认使用 ConfigureAwait(false),测试时用 async Task 而非.Result,I/O 用原生异步 API,CPU 密集型操作勿伪异步。
Stephen Cleary 是 C# 异步编程领域公认的实战派专家,他不是只讲理论的学者,而是长期在真实项目中踩坑、填坑、提炼模式的工程师。他的核心建议不是“怎么写 async”,而是“怎么不写错 async”——尤其在跨线程上下文、测试、UI 集成和库设计等容易静默崩溃的场景。
async void,除非你真在写事件处理程序这是 Cleary 反复强调的“第一戒律”。async void 方法无法被 await,异常会直接抛到 SynchronizationContext(比如 UI 线程),导致应用崩溃且难以捕获;它也不支持超时、取消或组合(如 Task.WhenAll)。
button_Click)可 async void,因为事件签名强制返回 void
async void DoWork() —— 改为 async Task DoWorkAsync()
Console.Main 在 .NET 6+ 允许 async Task Main(),但旧版或类库中仍需避免 void
ConfigureAwait(false) 除非你明确需要上下文默认情况下,await 会尝试“回到原上下文”(如 UI 线程或 ASP.NET 请求上下文)。这在库代码中是危险的:它可能引发死锁(尤其调用 .Result 或 .Wait() 时),也拖慢性能。
await 后加 .ConfigureAwait(false),例如 await stream.ReadAsync(buffer).ConfigureAwait(false)
HttpContext 访问ConfigureAwaitChecker)自动检测遗漏点.Result 或 .Wait()
Cleary 明确指出:“单元测试里出现 .Result,基本等于埋雷”。它会阻塞当前线程,在 xUnit/NUnit 的同步测试上下文中极易触发死锁(尤其涉及 SynchronizationContext 时)。
async Task,并 await 被测方法[Fact]
public void TestGetData() {
var result = GetDataAsync().Result; // ⚠️ 死锁高发区
Assert.Equal("OK", result);
}async Task 测试方法;NUnit 需 ≥ 3.0 并启用 AsyncTest 特性异步 ≠ 并行。Cleary 强调:await Task.Run(() => ComputeHeavy()) 不是真正的异步 I/O,只是把同步工作扔进线程池——它解决的是 UI 响应问题,但会增加调度开销,且不能缩放。
HttpClient.GetAsync、FileStream.ReadAsync)Parallel.ForEach 或 PLINQ,或明确用 Task.Run 并注明“此为线程池卸载,非异步”async Task CalculateAsync() { await Task.Run(() => ExpensiveMath()); } —— 方法名带 Async 却无真正异步语义,误导调用方真正难的从来不是“怎么让代码跑起来”,而是“怎么让它在并发、中断、失败、升级后依然可靠”。Cleary 的建议之所以被广泛采纳,是因为他始终站在调用链下游、测试覆盖率、部署稳定性这些真实战场说话——比如一个 ConfigureAwait(false) 的缺失,可能在压测时才暴露为 5% 的随机超时,而没人能复现。