Asp.Net Core 中的并发控制:SemaphoreSlim 深入理解与实战
摘要
SemaphoreSlim 是 .NET 中用于限制同时访问某些资源的轻量级信号量(可同步也可异步等待)。本文通过生活类比、核心 API 详解、常见场景和可运行示例,帮助你在 ASP.NET Core 或后台任务中安全地控制并发。
生活化类比:餐厅与停车场
- 餐厅桌位:想象餐厅只有 5 张桌子(槽位),每当顾客入座时就占用一个桌位;离开时释放一个。SemaphoreSlim 就像这 5 张桌子,限制同时就坐的人数,而不是把餐厅完全锁住(不是互斥)。
- 停车场:停车场有固定车位数,车辆到来若无空位就需要等待或离开(超时/取消)。SemaphoreSlim 允许多个“访客”并发访问,直到槽用尽。
基本概念
- initialCount:初始可用槽数(可同时通过的许可数量)。
- maxCount:最大槽数,Release 不可超过它。
- SemaphoreSlim 是托管内存中的轻量级实现,性能优于内核级 Semaphore,适合绝大多数场景。
- 实现了 IDisposable,长期占用时可释放。
常用 API(核心)
- Wait() / Wait(int milliseconds) — 同步阻塞等待许可。
- WaitAsync() / WaitAsync(CancellationToken) / WaitAsync(TimeSpan, CancellationToken) — 异步等待(推荐用于异步代码,避免线程阻塞)。
- Release() — 释放一个许可,必须与成功的 Wait/WaitAsync 对应。
- CurrentCount — 当前可用许可数(只读)。
异步使用模式(推荐)
始终在 finally 中 Release,以防止因异常/取消而导致许可泄露:
// 示例:异步任务内部获取并释放许可
await semaphore.WaitAsync(ct);
try
{
// 执行业务逻辑
}
finally
{
semaphore.Release();
}
超时与取消
- 使用 CancellationToken 能在等待期间取消 WaitAsync。
- 可用 WaitAsync(TimeSpan, CancellationToken) 或 Wait(timeout) 实现超时逻辑,注意区分返回值与抛出异常的行为:
- WaitAsync(TimeSpan) 会返回一个 bool(在 .NET 版本不同表现略有差异),建议捕获超时并按需处理。
- Wait/Wait(int) 在超时或取消时会返回 false 或抛出 OperationCanceledException(视调用方式而定)。
示例:带超时与取消
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
if (await semaphore.WaitAsync(TimeSpan.FromSeconds(2), cts.Token))
{
try
{
// 执行
}
finally
{
semaphore.Release();
}
}
else
{
// 处理等待超时
}
常见注意事项(要点)
- Release 必须与每次成功的 Wait/WaitAsync 对应(通常使用 try/finally)。
- 不要多次 Release 超出 maxCount,否则会抛出 SemaphoreFullException。
- SemaphoreSlim 不是互斥锁(Mutex):它允许多个并发访问(值 > 1)。
- 若长期持有 SemaphoreSlim,出于资源管理可以实现并调用 Dispose()。
- 在 Web 场景中(ASP.NET Core),尽量避免在请求线程上使用同步 Wait(),推荐使用 WaitAsync()。
与其他同步原语的对比
- lock / Monitor:用于互斥访问(只能 1 个线程),适合保护共享数据结构。
- Mutex:内核级,支持跨进程,但性能较低。
- SemaphoreSlim:限制并发数量、支持异步等待、轻量高效。
- Semaphore:内核级 Semaphore 支持跨进程,通常不如 SemaphoreSlim 高效(托管场景优先使用 SemaphoreSlim)。
实用场景举例
- 限制并发请求数(例如每次最多同时发起 5 个外部 API 调用)。
- 控制并发任务处理以减少对数据库或文件句柄的争用。
- 后台队列消费者并发数控制。
代码演示(完整可运行 C# Demo)
下面示例演示:先不限制并发,再用 SemaphoreSlim 将并发限制为 3 个;并演示超时/取消的典型使用方式。
using System;
using System.Threading;
using System.Threading.Tasks;
class SemaphoreSlimDemo
{
// 限制同时执行的任务数量为3
private static SemaphoreSlim semaphore = new SemaphoreSlim(3, 3);
static async Task Main(string[] args)
{
Console.WriteLine("SemaphoreSlim 演示开始");
Console.WriteLine("========================\n");
Console.WriteLine("1. 无限制并发执行:");
await WithoutSemaphore();
Console.WriteLine("\n" + new string('-', 50) + "\n");
Console.WriteLine("2. 使用 SemaphoreSlim 限制并发 (最多3个):");
await WithSemaphore();
Console.WriteLine("\n演示结束");
}
static async Task WithoutSemaphore()
{
var tasks = new Task[6];
for (int i = 0; i < 6; i++)
{
int taskId = i + 1;
tasks[i] = Task.Run(async () =>
{
Console.WriteLine($"任务 {taskId} - 开始执行 (无限制) at {DateTime.Now:HH:mm:ss.fff}");
await Task.Delay(1000);
Console.WriteLine($"任务 {taskId} - 执行完成 at {DateTime.Now:HH:mm:ss.fff}");
});
}
await Task.WhenAll(tasks);
}
static async Task WithSemaphore()
{
var tasks = new Task[6];
for (int i = 0; i < 6; i++)
{
int taskId = i + 1;
tasks[i] = Task.Run(async () =>
{
// 带取消/超时示例:如果等待超过 2 秒则放弃
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
bool acquired = false;
try
{
acquired = await semaphore.WaitAsync(TimeSpan.FromSeconds(2), cts.Token);
if (!acquired)
{
Console.WriteLine($"任务 {taskId} - 等待许可超时,放弃执行");
return;
}
Console.WriteLine($"任务 {taskId} - 获得许可,开始执行 at {DateTime.Now:HH:mm:ss.fff}");
await Task.Delay(1000, cts.Token); // 模拟工作
Console.WriteLine($"任务 {taskId} - 执行完成,准备释放许可 at {DateTime.Now:HH:mm:ss.fff}");
}
catch (OperationCanceledException)
{
Console.WriteLine($"任务 {taskId} - 被取消");
}
finally
{
if (acquired)
semaphore.Release();
}
});
}
await Task.WhenAll(tasks);
}
}
小结
- SemaphoreSlim 是控制并发的常用而高效的工具,尤其适合需要限制“并发数量”的场景。
- 在异步代码中优先使用 WaitAsync,并在 finally 中释放许可;使用 CancellationToken 和超时来提高健壮性。
- 结合具体场景(外部 API、数据库、文件 IO)合理设置并发上限,既能提高吞吐又能保护下游服务。

浙公网安备 33010602011771号