AddPolicyHandler未生效主因是策略未绑定DI托管的HttpClient或未覆盖目标异常/状态码;Retry、Timeout、CircuitBreaker需按外到内顺序组合,Timeout置最外层;共享熔断器须注册为单例;TaskCanceledException需结合CancellationToken和InnerException区分超时与取消。

HttpClient.AddPolicyHandler 为什么没生效
常见现象是加了 AddPolicyHandler 却发现请求失败一次就抛异常,重试根本没触发。根本原因通常是策略没绑定到正确的 HttpClient 实例,或者策略本身没覆盖实际发起请求的 HTTP 方法/状态码。
实操建议:
- 确认你是在
IServiceCollection中用AddHttpClient注册的客户端,而不是直接 new() HttpClient()—— Polly 策略只对 DI 容器管理的客户端生效 - 检查策略是否用了
HandleResult或Handle明确捕获目标:比如网络异常要写Handle,HTTP 503 要写() HandleResult(r => r.StatusCode == HttpStatusCode.ServiceUnavailable) - 确保没有在调用时手动 await 了
SendAsync并忽略返回值 —— Polly 只包装SendAsync,不干预GetAsync等封装方法内部逻辑(除非你用PolicyHttpMessageHandler手动套)
Retry + Timeout + CircuitBreaker 怎么串起来不打架
三个策略叠加时最容易踩的坑是超时和重试时间冲突:比如设了 3 次重试、每次间隔 1 秒,但总超时只设了 2 秒,结果第一次请求卡住 1.5 秒,第二次还没发出去就被 Timeout 给砍掉了。
实操建议:
- Timeout 策略必须放在最外层(即最后执行),否则内层重试可能被提前中断;用
Policy.WrapAsync时按「最外→最内」顺序传参:WrapAsync(timeoutPolicy, retryPolicy, circuitBreakerPolicy) - 重试间隔别用固定值,改用
WaitAndRetryAsync的指数退避,比如Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(0.5), retryCount: 3),避免雪崩式重连 - 熔断器的
failureThreshold别设太低(如 0.3),尤其在高并发下容易误熔;建议搭配SamplingDuration和MinimumThroughput使用,比如 30 秒内至少 20 次调用才开始统计
HttpClientFactory 创建的客户端怎么共享熔断器实例
默认每次调用 AddHttpClient 都会新建独立的熔断器,导致多个客户端无法协同感知服务故障。这不是 bug,是设计使然 —— 但你要共享状态就得显式控制生命周期。
实操建议:
- 把
CircuitBreakerPolicy提前注册为单例服务:services.AddSingleton>(sp => Policy .Handle ().CircuitBreakerAsync(...)) - 在
AddHttpClient里用AddPolicyHandler直接传入这个单例策略对象,而不是每次都 new 一个新策略 - 注意:共享熔断器意味着所有使用它的客户端共用同一个状态机,如果某客户端专门调用不稳定的测试接口,可能把整个熔断器拖进 Open 状态 —— 生产环境建议按业务域或下游服务拆分策略实例
TaskCanceledException 是超时还是用户取消
当看到 TaskCanceledException,不能直接认为是 Polly 的 timeout 触发的。它可能是:① CancellationToken 被外部主动取消(比如 ASP.NET Core 请求被客户端断开);② TimeoutPolicy 主动 cancel;③ HttpClient.Timeout 原生超时(此时不会走 Polly 流程)。
实操建议:
- 区分方式看异常的
CancellationToken.IsCancellationRequested和InnerException:Polly 的 timeout 会包装成TimeoutRejectedException,而原生HttpClient.Timeout抛的是未包装的TaskCanceledException - 务必关闭
HttpClient.Timeout(设为TimeSpan.Zero),让所有超时都由 Polly 统一管控,否则策略行为不可预测 - 日志里记录
policyKey和原始异常类型,比单纯 catchException更容易定位问题源头
策略组合真正难的不是写法,而是理解每层 cancel 的来源和传播路径 —— 尤其在有 cancellation token 透传、中间件拦截、Kestrel 连接超时等多重机制共存时,异常堆栈里那个 TaskCanceledException 往往藏得最深。











