BulkheadPolicy 是 Polly 中限制并发执行数量的隔离机制,通过固定容量“舱壁”控制对外部依赖的并发调用,超出则立即失败或排队;需指定 maxParallelization(≥1)和 maxQueuingActions(0 表示不排队),拒绝时抛 BulkheadRejectedException,须显式捕获。

什么是 Polly 的 BulkheadPolicy?
它不是“限流”也不是“熔断”,而是限制**并发执行数量**的隔离机制:当多个请求同时调用同一外部依赖(比如数据库连接池、HTTP 客户端),BulkheadPolicy 会把它们塞进一个固定容量的“舱壁”里,超出的请求立刻失败(或排队等待,取决于配置),避免一个慢依赖拖垮整个线程池或耗尽资源。
如何创建并使用 BulkheadPolicy?
核心是 Polly.Bulkhead.BulkheadPolicy 类型,需指定最大并发数 maxParallelization 和最大排队数 maxQueuingActions。注意:Polly v8+ 已将 Bulkhead 合并进主包,无需额外安装 Polly.Bulkhead 子包。
var bulkhead = Policy.BulkheadAsync(
maxParallelization: 3, // 同时最多 3 个任务在执行
maxQueuingActions: 5 // 最多允许 5 个任务在队列中等待
);
await bulkhead.ExecuteAsync(async () =>
{
// 这里放可能耗时/不稳定的调用,例如:
await httpClient.GetAsync("https://api.example.com/data");
});
-
maxParallelization必须 ≥ 1;设为 1 就等效于串行化访问 -
maxQueuingActions设为 0 表示不排队,超限时直接抛BulkheadRejectedException - 如果队列满且未启用排队(即
maxQueuingActions == 0),会立即失败,不会阻塞线程
为什么 ExecuteAsync 抛出 BulkheadRejectedException 却没被捕获?
这是常见疏忽:Polly 的 BulkheadPolicy 在拒绝请求时抛的是 BulkheadRejectedException,不是 Exception 基类。若只写 catch (Exception),这个异常会漏掉。
try
{
await bulkhead.ExecuteAsync(() => DoRiskyWork());
}
catch (BulkheadRejectedException ex) // 必须显式 catch 这个类型
{
// 处理被拒请求:返回降级值、打日志、触发告警等
Log.Warning(ex, "Request rejected by bulkhead");
return new FallbackResult();
}
- 别依赖全局异常处理器自动捕获它——它不是
AggregateException的子类,也不会被Task.Wait()包装 - 如果你用的是
PolicyWrap(比如套了重试 + 舱壁),确保BulkheadPolicy在最内层,否则拒绝逻辑可能被外层策略干扰
和 SemaphoreSlim 手动限流比有什么区别?
本质都是控制并发,但 BulkheadPolicy 提供了更贴近业务语义的抽象:它自带队列管理、拒绝统计、与其它 Polly 策略(如 RetryPolicy、CircuitBreakerPolicy)天然可组合,且拒绝行为是同步判断的(不真正 await),而 SemaphoreSlim.WaitAsync() 是纯底层信号量,容易误写成死锁或忽略超时。
- 不要在
ExecuteAsync内部再套一层SemaphoreSlim—— 会造成双重并发控制,逻辑混乱 -
BulkheadPolicy的队列是内存中的 FIFO,不持久化,服务重启即清空 - 监控指标如
bulkhead.CurrentlyExecuting和bulkhead.CurrentlyQueued需通过Context或自定义事件获取,没有开箱即用的 Metrics 输出
舱壁的关键不在“设个数字”,而在理解哪些依赖真正需要隔离、这个数字是否随负载动态调整、以及被拒后系统是否具备可观测性和应对路径。硬编码 maxParallelization: 5 很容易在压测时暴露为瓶颈点。










