C产品如何满足特定用户需求?
- 内容介绍
- 文章标签
- 相关推荐
本文共计859个文字,预计阅读时间需要4分钟。
SemaphoreSlim 限制并发访问,确保线程安全。适用于控制对共享资源的访问,防止竞态条件。
为什么不能在方法里 new SemaphoreSlim(5)
每次调用都新建实例 → 每个请求拿到的都是全新信号量,初始许可数全是 5,完全不共享计数。限流形同虚设。
常见表现:压测时并发数轻松突破设定值,日志里看到几十个请求同时打下游。
正确做法:
- ASP.NET Core 中注册为
Singleton:services.AddSingleton(new SemaphoreSlim(5, 5)) - 或声明为
private static readonly SemaphoreSlim _sem = new SemaphoreSlim(5, 5) - 绝对不要写成
var sem = new SemaphoreSlim(5)放在方法体或using块里
WaitAsync() 后 Release() 必须写在 finally 里
这是线上最常导致服务卡死的点:一次 Release() 漏掉,许可计数就永久少 1;漏三次,第 4 个请求开始永远 WaitAsync() 挂起。
错误写法示例:
await _sem.WaitAsync(); try { await DoSomethingAsync(); _sem.Release(); // 异常一抛,这行根本不会执行 } catch { ... }
正确结构只有一种:
await _sem.WaitAsync(); try { await DoSomethingAsync(); } finally { _sem.Release(); // 同步调用,不 await }
注意:Release() 不能放 try 里,也不能放 catch 里重写一遍——重复释放会导致许可溢出(比如从 0 变成 1),后续限流彻底失准。
超时和取消必须显式处理,且别信 WaitAsync(TimeSpan) 的返回值
.NET 6+ 中,WaitAsync(TimeSpan) 重载已废弃,超时直接抛 OperationCanceledException,**不返回 false**。
容易踩的坑:
- 只
catch (Exception)却没专门捕获OperationCanceledException,结果超时被吞,逻辑误判为成功 - 用
if (!await _sem.WaitAsync(ts))—— 这段代码在新版本编译不过或永远不进分支 - 传入的
CancellationToken被忽略,导致外部主动取消(如 HTTP 请求中止)无法及时响应
推荐写法:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); try { await _sem.WaitAsync(cts.Token); } catch (OperationCanceledException) when (!cts.Token.IsCancellationRequested) { throw new TimeoutException("获取并发许可超时"); }
别把它套在 HttpClient.SendAsync 外层当“锁”用
HttpClient 本身有 MaxConnectionsPerServer 和连接池管理,再叠一层 SemaphoreSlim 只是掩盖真实瓶颈,还可能因异常漏 Release() 引发死锁。
真正该控的是「请求发起节奏」,不是「连接执行状态」。
安全封装的关键点:
- 把
WaitAsync()放在构造完HttpRequestMessage之后、调用SendAsync()之前 - 整个
SendAsync()调用必须包在try/finally内,哪怕抛HttpRequestException也要Release() - 不要对健康检查、静态资源路径做限流,否则
/health接口自己先卡住
一句话收尾:SemaphoreSlim 的本质是计数门控,不是线程锁;它的脆弱点不在语法,而在生命周期管理、异常路径覆盖和超时语义理解——漏掉任一环,限流就从保护变成隐患。
本文共计859个文字,预计阅读时间需要4分钟。
SemaphoreSlim 限制并发访问,确保线程安全。适用于控制对共享资源的访问,防止竞态条件。
为什么不能在方法里 new SemaphoreSlim(5)
每次调用都新建实例 → 每个请求拿到的都是全新信号量,初始许可数全是 5,完全不共享计数。限流形同虚设。
常见表现:压测时并发数轻松突破设定值,日志里看到几十个请求同时打下游。
正确做法:
- ASP.NET Core 中注册为
Singleton:services.AddSingleton(new SemaphoreSlim(5, 5)) - 或声明为
private static readonly SemaphoreSlim _sem = new SemaphoreSlim(5, 5) - 绝对不要写成
var sem = new SemaphoreSlim(5)放在方法体或using块里
WaitAsync() 后 Release() 必须写在 finally 里
这是线上最常导致服务卡死的点:一次 Release() 漏掉,许可计数就永久少 1;漏三次,第 4 个请求开始永远 WaitAsync() 挂起。
错误写法示例:
await _sem.WaitAsync(); try { await DoSomethingAsync(); _sem.Release(); // 异常一抛,这行根本不会执行 } catch { ... }
正确结构只有一种:
await _sem.WaitAsync(); try { await DoSomethingAsync(); } finally { _sem.Release(); // 同步调用,不 await }
注意:Release() 不能放 try 里,也不能放 catch 里重写一遍——重复释放会导致许可溢出(比如从 0 变成 1),后续限流彻底失准。
超时和取消必须显式处理,且别信 WaitAsync(TimeSpan) 的返回值
.NET 6+ 中,WaitAsync(TimeSpan) 重载已废弃,超时直接抛 OperationCanceledException,**不返回 false**。
容易踩的坑:
- 只
catch (Exception)却没专门捕获OperationCanceledException,结果超时被吞,逻辑误判为成功 - 用
if (!await _sem.WaitAsync(ts))—— 这段代码在新版本编译不过或永远不进分支 - 传入的
CancellationToken被忽略,导致外部主动取消(如 HTTP 请求中止)无法及时响应
推荐写法:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); try { await _sem.WaitAsync(cts.Token); } catch (OperationCanceledException) when (!cts.Token.IsCancellationRequested) { throw new TimeoutException("获取并发许可超时"); }
别把它套在 HttpClient.SendAsync 外层当“锁”用
HttpClient 本身有 MaxConnectionsPerServer 和连接池管理,再叠一层 SemaphoreSlim 只是掩盖真实瓶颈,还可能因异常漏 Release() 引发死锁。
真正该控的是「请求发起节奏」,不是「连接执行状态」。
安全封装的关键点:
- 把
WaitAsync()放在构造完HttpRequestMessage之后、调用SendAsync()之前 - 整个
SendAsync()调用必须包在try/finally内,哪怕抛HttpRequestException也要Release() - 不要对健康检查、静态资源路径做限流,否则
/health接口自己先卡住
一句话收尾:SemaphoreSlim 的本质是计数门控,不是线程锁;它的脆弱点不在语法,而在生命周期管理、异常路径覆盖和超时语义理解——漏掉任一环,限流就从保护变成隐患。

