用 Policy.WrapAsync(或 Wrap)将多个策略按执行顺序嵌套包装,外层控整体(如超时),内层处理细节(如重试、熔断);顺序决定生效优先级,类型须一致,异常为最内层原始异常。

多个Polly策略怎么串起来用
直接说结论:用 Policy.WrapAsync(或 Policy.Wrap)把多个策略按执行顺序“包”成一个,不是简单叠加,而是形成嵌套调用链。外层策略控制整体行为(比如超时),内层策略处理具体异常(比如重试、熔断)。
常见错误是把 RetryPolicy 和 CircuitBreakerPolicy 分开 await,结果重试逻辑完全绕过熔断器;或者误用 .Or<texception>()</texception> 想合并策略——那只是扩展异常类型匹配,不是组合策略行为。
-
Wrap的顺序很重要:最外层策略最先生效,最内层策略最接近实际委托。例如TimeoutPolicy.WrapAsync(RetryPolicy.WrapAsync(CircuitBreakerPolicy))表示:先走熔断判断 → 再决定是否重试 → 最后被超时兜底中断 - 所有被包装的策略必须类型一致:要么全是
AsyncPolicy<tresult></tresult>,要么全是AsyncPolicy(无返回值)。混用会导致编译失败 - 如果策略里用了
Context(比如传"operation-id"),外层策略的Context会透传给内层,但各策略的onRetry/onBreak回调中拿到的Context是同一份引用,注意别意外覆盖
带返回值的异步策略怎么包装
处理 HTTP 调用这类有返回值的场景,必须用泛型版本的 WrapAsync<tresult></tresult>,否则编译不通过。策略声明和包装都得对齐类型。
例如封装一个带熔断 + 重试 + 超时的 HttpClient.GetAsync<string></string> 调用:
var circuitBreaker = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(5, TimeSpan.FromMinutes(1));
<p>var retry = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2) });</p><p>var timeout = Policy.TimeoutAsync<HttpResponseMessage>(5);</p><p>var wrapped = Policy.WrapAsync(circuitBreaker, retry, timeout); // 注意顺序</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill11164" title="jm-jsjkxyjs02-zlm-405"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20e352aa140.jpg" alt="jm-jsjkxyjs02-zlm-405" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill11164" title="jm-jsjkxyjs02-zlm-405">jm-jsjkxyjs02-zlm-405</a>
<p>使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景:
(1) 暂存更改时需要提交信息;
(2) 审查 diff 时希望获得结构化摘要;
(3) 编写 PR 描述或变更日志(changelog)条目;
(4) 用户明确要求“为本次更改编写提交信息”。
该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。</p>
</div>
<a href="/xiazai/skill11164" title="jm-jsjkxyjs02-zlm-405" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><p>// 使用时:
var response = await wrapped.ExecuteAsync(() => client.GetAsync("<a href="https://www.php.cn/link/710ba53b0d353329706ee1bedf4b9b39">https://www.php.cn/link/710ba53b0d353329706ee1bedf4b9b39</a>"));关键点:OrResult 用于捕获“成功返回但业务失败”的情况(如 500 响应),它和 Handle<t></t> 是并列条件,不是嵌套关系;TimeoutAsync<tresult></tresult> 的泛型参数必须和实际委托返回类型严格一致。
同步策略包装要注意什么
同步策略用 Policy.Wrap,但实际项目中极少单独使用——因为现代 C# I/O 几乎都是异步的。唯一常见同步场景是包装本地计算型操作(比如 JSON 解析、内存缓存查找),且明确不希望引入 async/await 开销。
- 不要把异步策略塞进
Policy.Wrap:比如把Policy.Handle<exception>().RetryAsync()</exception>传给Wrap,编译报错Cannot convert async lambda - 同步包装链里所有策略必须是
Policy<tresult></tresult>或Policy,不能混入AsyncPolicy类型 - 如果原操作是
Func<tresult></tresult>,包装后调用Execute;如果是Action,则用Execute无返回版
策略组合后异常怎么捕获
包装后的策略执行失败时,抛出的异常是**最内层触发策略的原始异常**,不会自动包装成新类型。比如熔断器打开时抛 BrokenCircuitException,超时抛 TimeoutRejectedException,重试耗尽抛最后一次的 HttpRequestException。
这意味着你不能只 catch Exception 就完事,得按需区分处理:
- 想统一记录所有失败?用
ExecuteAndCaptureAsync获取PolicyResult<tresult></tresult>,检查FinalException属性 - 想对超时单独告警?在
ExecuteAsync外层 try/catchTimeoutRejectedException - 熔断状态变化需要监听?必须在定义
CircuitBreakerPolicy时传入onBreak和onReset回调,包装后这些回调依然有效
最容易被忽略的是:当多个策略都配置了 onRetry,它们会按包装顺序从外到内依次触发——但如果你没给每个策略配不同的日志前缀,根本分不清哪条日志属于哪个策略。

















