在 .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 的区别:避免在异步代码中使用普通 locklock 会阻塞线程,而 WaitAsync() 会在等待时释放线程,让其去做其他事。

结论

async/await 为我们提供了强大的能力,但 SemaphoreSlim 为我们提供了控制权。不要让服务器因为一次性尝试执行太多操作而崩溃。明智地限制并发,你的应用将保持响应快速且稳定。

Back to Blog

相关文章

阅读更多 »