Async/Await 不要乱用!很多 .NET 开发者都理解错了异步

📅 2026/8/2 5:49:05 👁️ 阅读次数 📝 编程学习
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 CoreSaveChangesAsync

数据库 I/O

HttpClientSendAsync

网络 I/O

StreamReadAsync

文件 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

你可以问自己三个问题:

  1. 我在等什么?

  2. 它在真正异步吗?

  3. 我不 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 边界

能写出无状态机的高性能代码