Semaphore 不适合现代异步代码,因其无 WaitAsync 方法,await semaphore.WaitOne() 会编译报错;必须用 SemaphoreSlim——它原生支持 await、CancellationToken 和进程内轻量限流,且需 static readonly 声明、配对使用 WaitAsync/Release 并置于 try/finally 中确保释放。

Semaphore 不适合现代异步代码,别在 async 方法里硬套它——它没有 WaitAsync,编译直接报错;真要限流并发,该用 SemaphoreSlim。
为什么 Semaphore.WaitOne() 不能 await
Semaphore 是 .NET Framework 1.1 就存在的同步原语,纯内核对象实现,天生不支持异步等待。你写 await semaphore.WaitOne() 会编译失败,不是语法问题,是类型根本没这个方法。
常见错误现象:
- IDE 提示 “
Semaphoredoes not contain a definition for ‘WaitAsync’” - 误以为
Task.Run(() => semaphore.WaitOne())就是异步——实际只是把阻塞挪到线程池线程上,仍浪费线程资源
真正需要异步等待(比如 HTTP 调用前限流、UI 响应不卡顿),必须换 SemaphoreSlim。
SemaphoreSlim 的正确初始化和复用方式
SemaphoreSlim 是托管实现,轻量、支持 CancellationToken、能 await,但仅限当前进程内使用——这恰恰覆盖了 95% 的业务场景(API 限流、DB 连接数控制、后台任务并发压制)。
关键实操建议:
- 始终作为
static readonly字段声明,避免反复 new:例如private static readonly SemaphoreSlim _throttle = new SemaphoreSlim(5, 5); - 两个构造参数都设为同一值(如
5, 5),防止初始许可数小于最大值导致逻辑绕弯 - 务必在
finally块中调用Release(),哪怕请求抛异常也要归还许可,否则信号量永久泄漏 - 可传入
CancellationToken到WaitAsync(cancellationToken),配合超时或页面关闭等主动取消场景
什么时候才非用 Semaphore 不可
只有跨进程同步才需要 Semaphore,比如多个独立的 .exe 进程共同访问同一个本地文件、共享内存或硬件设备。
但要注意:
- 必须用带名字的构造:
new Semaphore(0, 3, "MySharedResource") - Windows 对命名信号量有权限限制,普通用户常因
UnauthorizedAccessException失败,需显式配置SemaphoreSecurity - 命名不是随便起字符串,作用域受会话(session)和 UAC 影响,服务进程与交互式桌面进程默认无法互通
- 性能开销比
SemaphoreSlim高一个数量级,纯进程内用它属于过度设计
真正容易被忽略的是:信号量只管“同时有几个线程在跑”,不管“它们操作的数据是否线程安全”。即使用了 SemaphoreSlim(1, 1),对共享变量的读写仍需额外加锁或用原子操作,否则照样数据错乱。


















