SemaphoreSlim 是 C# 中轻量级的信号量实现,专为异步场景设计,适合控制并发访问资源的线程/任务数量。它比传统的
Semaphore更高效(不依赖操作系统内核),且原生支持
async/await。
初始化并设置最大并发数
构造函数接收两个参数:初始可进入数(通常等于最大并发数)、最大允许数。
比如限制最多 3 个任务同时执行:var semaphore = new SemaphoreSlim(3, 3);第一个参数是“当前可用许可数”,第二个是“上限”,两者相等表示初始全空闲 若设为
new SemaphoreSlim(0, 3),则初始无人能进入,需手动
Release()才能开始
在异步方法中安全获取和释放许可
务必用
await WaitAsync()获取许可,用
Release()归还(不能用
await ReleaseAsync(),它不存在)。 推荐写法(自动释放,防遗漏):
await semaphore.WaitAsync();
try
{
// 执行受控操作,如调用 API、处理文件等
await DoWorkAsync();
}
finally
{
semaphore.Release(); // 必须确保执行到
}
别用 using——
SemaphoreSlim不实现
IDisposable(仅当需要释放底层资源时才调用
Dispose(),一般不用) 避免在
catch块外直接
Release(),否则异常时会漏释放,导致许可永久丢失
配合 Task.WhenAll 控制批量任务并发
常见需求:启动 100 个 HTTP 请求,但只允许最多 5 个并发。
把所有任务拆成小批次,或用循环 +WaitAsync控制节奏 简洁做法(逐个提交,内部限流):
var tasks = urls.Select(async url =>
{
await semaphore.WaitAsync();
try
{
return await client.GetStringAsync(url);
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
注意:上面写法本质是“启动 100 个任务,但每个都先抢许可”,实际并发仍可控
如果想更明确分批(如每批 5 个),可用 Chunk()+
Task.WhenAll嵌套
其他实用细节
WaitAsync()可传入
CancellationToken,支持取消等待(比如超时或用户中止)
CurrentCount属性可读取当前剩余许可数(仅作监控,非原子判断依据) 不要在同步方法里用
Wait()阻塞等待 —— 容易引发死锁,尤其在 UI 或 ASP.NET 同步上下文中 若需跨进程或跨 AppDomain 限流,
SemaphoreSlim不适用,得换
Semaphore或分布式方案(如 Redis 信号量)
基本上就这些。SemaphoreSlim 不复杂但容易忽略释放和异常路径,只要守好“
try/finally + Release”这个模式,就能稳稳控住并发量。
