semaphoreslim是c#中专为异步限流设计的轻量级信号量,支持await、不阻塞线程、可硬性限制并发数;lock/monitor仅适用于互斥,高并发下易耗尽线程池,且不支持异步。

SemaphoreSlim 是 C# 中控制并发请求数最直接、最轻量的方案,不是靠 lock 或 Monitor 模拟,也不推荐用 TaskLimiter 这类包装库——除非你明确需要排队调度或优先级控制。
为什么不用 lock / Monitor 做并发限制
它们是同步阻塞原语,在高并发下会快速耗尽线程池。比如 1000 个请求同时进来,lock 会让 999 个线程在等待队列里挂起,CPU 空转、内存堆积、响应延迟飙升。而 SemaphoreSlim 是纯用户态、支持 await 的异步信号量,不占线程,等待时让出控制权。
- 错误写法:
lock (_syncObj) { await DoWorkAsync(); }——lock不支持await,编译不过;强行用Monitor.Enter+try/finally会阻塞线程 - 正确前提:所有限流逻辑必须在
async方法内,用WaitAsync()+Release() - 性能影响:单次
WaitAsync()调用开销约几十纳秒,远低于线程调度成本
ASP.NET Core 全局接口并发限制怎么配
别在每个 Controller 方法里手写 await _sem.WaitAsync() —— 易漏、难统一、无法跳过健康检查路径。
- 在
Program.cs注册单例:services.AddSingleton(new SemaphoreSlim(8))(注意:必须是Singleton,Scoped会导致限流失效) - 写一个
ActionFilter,在OnActionExecutionAsync中调用await _sem.WaitAsync(TimeSpan.FromSeconds(2)) -
try/finally里确保_sem.Release(),哪怕抛异常也要释放 - 加路径白名单:对
/health、/metrics、/swagger等运维端点跳过限流
SemaphoreSlim 和 TaskLimiter 到底选哪个
TaskLimiter(来自 Microsoft.Extensions.Concurrency)本质是 SemaphoreSlim 的封装,加了队列和取消逻辑。多数场景没必要引入。
- 用
SemaphoreSlim就够了:只要目标是“硬性限制并发数”,比如“绝不允许超过 5 个下游 HTTP 请求同时发出” - 警惕
TaskLimiter的默认行为:它不限制排队长度,突发流量下任务堆积,MemoryCache或 GC 压力陡增 -
SemaphoreSlim支持超时失败,能更快返回 429 或降级响应;TaskLimiter默认无限排队,容易掩盖问题 - 兼容性:
SemaphoreSlim从 .NET 4.5 就有;TaskLimiter要求 .NET 6+
最容易被忽略的三个坑
许可证泄漏、作用域错配、异步上下文丢失,这三个问题一旦发生,服务会悄无声息地卡死。
-
Release()必须在finally块里执行,不能只放在try末尾——异常一抛,许可证就永远锁住 -
SemaphoreSlim实例绝不能注册为Scoped或Transient,否则每个请求拿到新实例,限流形同虚设 - 避免在
async void方法(如事件处理)中使用它;后台任务若没await或没捕获异常,Release()就不会触发











