Async/Await 不要乱用!很多 .NET 开发者都理解错了异步
Async/Await 不要乱用!很多 .NET 开发者都理解错了异步
“加了
async/await,性能就变好了?”“所有方法都写成
async,就是异步编程?”错。而且错得很危险。
async/await是 C# 5 带来的神兵利器,但也成了滥用最严重的语法糖之一。今天这篇文章,我们来把异步这回事,彻底讲清楚、讲正确、讲到能落地。
一、先给结论(非常重要)
✅async/await≠ 多线程
✅async/await≠ 更快
✅async/await是为了“释放线程”,不是“创造线程”
✅没有 I/O 等待,就别用 async/await
一句话总结:
Async/Await 是为 I/O 密集型操作准备的,不是为了让你“显得现代”。
二、90% 的开发者对 async/await 的误解
❌ 误解 1:async/await会创建新线程
public async Task DoWorkAsync() { await Task.Delay(1000); }很多人以为这里“开了一个线程”。
真相:
Task.Delay使用的是Timer没有任何线程被阻塞
也没有新线程被创建
👉await的本质是:“我现在没事干,把线程让出来给别人用”
❌ 误解 2:用了 async,程序就快了
public async Task<int> Compute() { return await Task.Run(() => { Thread.Sleep(1000); return 1 + 1; }); }这是典型的伪异步。
CPU 计算仍然在 ThreadPool 里跑
还多了状态机开销
反而更慢
✅ 正确认知:
场景 | 是否该用 async |
|---|---|
HTTP 请求 | ✅ |
数据库访问 | ✅ |
文件 I/O | ✅ |
密集计算 | ❌ |
内存排序 / 循环 | ❌ |
❌ 误解 3:所有方法都 async 才“规范”
public async Task<string> GetNameAsync() { return "Tom"; }🚨这是反模式
没有
await编译器直接警告
白白生成状态机
✅ 正确写法:
public Task<string> GetNameAsync() { return Task.FromResult("Tom"); }三、Async/Await 到底做了什么?
1️⃣ 本质:状态机(State Machine)
async方法编译后会变成类似这样的东西:
保存上下文
注册回调
在 await 后恢复执行
代价:
堆分配
上下文切换
代码复杂度上升
👉 所以:不该 async 的地方,坚决不用
2️⃣ await 的真正含义
await xxxAsync();等价于:
“如果 xxxAsync 没完成,我就把当前线程还给线程池;等它完成了,再回来接着跑。”
这才是异步的核心价值:高并发下的线程利用率。
四、最经典的误用场景(你一定见过)
🚨 在 ASP.NET Core 中使用Task.Run
public async Task<IActionResult> Get() { var data = await Task.Run(() => { return _service.DoWork(); }); return Ok(data); }问题:
ASP.NET Core 本来就用 ThreadPool
Task.Run又去抢线程没有任何收益,只有损耗
✅ 正确做法:
public async Task<IActionResult> Get() { var data = await _service.DoWorkAsync(); return Ok(data); }🚨 同步方法中调用异步(死锁高发)
public int Get() { return GetAsync().Result; }在ASP.NET / WinForms / WPF 中,这几乎必死锁。
✅ 正确做法:
一路 async 到底
或明确使用
ConfigureAwait(false)
await GetAsync().ConfigureAwait(false);五、什么时候必须用 async/await?
✅ 必须用的场景
场景 | 原因 |
|---|---|
EF Core | 数据库 I/O |
HttpClient | 网络 I/O |
Stream | 文件 I/O |
SignalR / gRPC | 长连接 |
高并发 Web API | 提高吞吐量 |
❌ 不要用 async 的场景
场景 | 原因 |
|---|---|
内存计算 | 无等待 |
加解密 | CPU 密集 |
循环 / LINQ | 无 I/O |
简单 CRUD(无异步 API) | 无意义 |
六、ConfigureAwait(false) 到底要不要写?
ASP.NET Core 中:
✅基本不需要
ASP.NET Core 没有
SynchronizationContext默认不会回到原线程
类库中:
✅强烈推荐
await _db.SaveChangesAsync().ConfigureAwait(false);原因:
防止死锁
减少上下文切换
提高库的可复用性
七、性能陷阱:async 的隐藏成本
1️⃣ 状态机分配
public async Task<int> A() { return await B(); }编译器生成:
状态机 struct → box → 堆
闭包捕获
多次跳转
✅ 优化写法:
public Task<int> A() { return B(); }这就是“直通式异步”(Async Passthrough)
2️⃣ ValueTask:不是银弹
public ValueTask<int> GetAsync() { if (_cache.TryGetValue(out var value)) return new ValueTask<int>(value); return new ValueTask<int>(LoadFromDbAsync()); }✅ 适合:
高频调用
大概率同步返回
❌ 不适合:
随意替换
Task新手滥用
八、一条黄金法则(背下来)
只有在“等待 I/O”的时候,才值得 async/await
你可以问自己三个问题:
我在等什么?
它在真正异步吗?
我不 await,会不会阻塞线程?
如果答案不是“等 I/O + 真异步”,那就不要用。
九、推荐的异步编码规范(团队可直接用)
✅命名
异步方法以
Async结尾
✅返回类型
无返回值:
Task有返回值:
Task<T>避免
void(除非事件)
✅禁止
Task.Run包装 CPU 工作.Result/.Wait()无 await 的 async 方法
✅类库
默认
ConfigureAwait(false)
十、总结一句话(写给所有 .NET 开发者)
Async/Await 是释放线程的工具,不是加速计算的魔法。
滥用 async,比不用 async 更可怕。
真正的异步高手,不是“哪里都写 async”,而是:
✅知道什么时候不该用 async
✅一眼能看出 I/O 边界
✅能写出无状态机的高性能代码