BulkheadPolicy 是 Polly 中实现舱壁隔离的策略,通过 maxParallelization(最大并发数)和 maxQueuedActions(最大排队数)限制资源使用,超限即抛 BulkheadRejectedException,本质是物理隔离而非限流。

什么是 Polly 的 BulkheadPolicy?
BulkheadPolicy 是 Polly 中实现舱壁隔离(Bulkhead Isolation)的策略,它通过限制并发执行的请求数量和排队等待的请求数量,防止某个依赖故障或延迟拖垮整个调用方线程池或资源。它的核心不是重试或熔断,而是“物理隔离”——像轮船的舱壁一样,把失败控制在局部。
BulkheadPolicy 的两个关键参数:maxParallelization 和 maxQueuedActions
创建 BulkheadPolicy 时必须指定这两个整数参数,它们共同定义了资源使用边界:
-
maxParallelization:最多允许多少个操作**同时执行**(即占用线程/任务) -
maxQueuedActions:当所有并行槽位被占满时,最多允许多少个操作在队列中**等待执行**
超出这两个限制的调用会立即抛出 BulkheadRejectedException,而不是阻塞或排队无限等待。
var bulkhead = Policy.BulkheadAsync(
maxParallelization: 5,
maxQueuedActions: 10
);
注意:maxParallelization 不等于线程数(尤其在 async/await 场景下),它表示“同时处于 ExecuteAsync 执行上下文中的操作数量”。对 I/O 密集型操作,实际线程消耗远小于此值;但对 CPU 密集型或同步阻塞调用,它可能直接对应线程池租用数。
为什么不能只靠 Task.Run 或 SemaphoreSlim?
手动用 SemaphoreSlim 或线程池限流,容易漏掉几个关键点:
- 不自动区分“执行中”和“排队中”,无法统一拒绝超限请求
- 异常处理分散:你得自己捕获
WaitAsync超时、手动抛出业务一致的拒绝异常 - 无法与 Polly 其他策略(如
RetryPolicy、CircuitBreakerPolicy)自然组合 - 缺少内置指标(如当前排队数、拒绝计数),调试和监控成本高
而 BulkheadPolicy 把这些封装进统一的 ExecuteAsync 接口,且支持 PolicyWrap 组合:
var policyWrap = Policy.WrapAsync(bulkhead, retry, breaker);
常见误用和坑点
舱壁策略最容易被当成“限流器”滥用,但它本质是**资源隔离机制**,不是速率控制器:
- 它不感知时间窗口(不像令牌桶),所以不能替代
RateLimitPolicy - 如果内部操作本身是同步阻塞(如
Thread.Sleep),maxParallelization会快速耗尽线程池,导致后续请求即使没超限也因无可用线程而卡死 -
maxQueuedActions设为 0 并不意味着“不排队”,而是“拒绝所有排队请求”——此时一旦 5 个并发满,第 6 个调用立刻抛BulkheadRejectedException - 异步操作中,不要在
ExecuteAsync内部再用.Wait()或.Result,这会破坏异步流并可能导致死锁或线程饥饿
真正需要舱壁的场景,是调用外部不稳定服务(如第三方 HTTP API、数据库查询),且你明确知道该服务的吞吐瓶颈或连接池上限。盲目加舱壁反而增加延迟和拒绝率。










