在 .NET 中,Async/Await 在没有限制时可能会浪费资源 🚀🛑
发布: (2025年12月28日 GMT+8 12:54)
3 min read
原文: Dev.to
Source: Dev.to
前言
我们很多人认为使用 async/await 会自动让应用“可伸缩”。然而,如果没有限流(throttling)机制,异步代码反而会成为吞噬资源的“主子”,瞬间耗尽服务器资源。本文(以及我最新的 YouTube 视频)将讨论为什么不设限的 Task.WhenAll 危险,以及 SemaphoreSlim 如何拯救你的服务器。
背后发生了什么?
- Socket 耗尽 – 服务器的出站端口被用光。
- 数据库连接池饥饿 – 所有数据库连接都被占用,导致其他请求被阻塞。
- 内存激增 – 每个任务都有自己的状态机和内存分配。
结果是服务器“卡死”、请求超时,用户体验急剧下降。
SemaphoreSlim
SemaphoreSlim 是 .NET 中最轻量的方式,用来限制同时运行的任务数量。
正确的实现方式
// 限制最多 10 个并发操作
using var semaphore = new SemaphoreSlim(10);
var tasks = dataList.Select(async d =>
{
await semaphore.WaitAsync(); // 在这里排队
try
{
await CallExternalApiAsync(d);
}
finally
{
semaphore.Release(); // 释放槽位给下一个任务
}
});
await Task.WhenAll(tasks);
有了这段代码,即使你有 1,000 条数据,也只能同时处理 10 条。服务器资源得以保持,应用依然响应迅速。
最佳实践
- 使用
try...finally:一定要在finally块中调用Release()。否则信号量槽位会“泄漏”,导致应用出现死锁。 - 设定合理的限制:不要随意写数字。应根据资源能力(例如数据库连接池的最大连接数)来确定上限。
- Semaphore 与 Lock 的区别:避免在异步代码中使用普通
lock。lock会阻塞线程,而WaitAsync()会在等待时释放线程,让其去做其他事。
结论
async/await 为我们提供了强大的能力,但 SemaphoreSlim 为我们提供了控制权。不要让服务器因为一次性尝试执行太多操作而崩溃。明智地限制并发,你的应用将保持响应快速且稳定。