Polly 超时策略后取消过滤器没命中,检查异常携带的 token
Polly 8.6.6 的超时策略会给回调换用内部 token,调用方主动取消后,异常仍可能携带这个内部值。8.7.0 已增加调用方 token 的传播;升级时仍需区分用户取消、策略超时和取消竞态。
给 Polly 管道加上 AddTimeout 后,用户明明主动取消了请求,外层的取消异常过滤器却没有命中:
catch (OperationCanceledException ex)
when (ex.CancellationToken == callerCancellationToken)
{
throw;
}
如果下一层是包装所有异常的分支,取消甚至会被改写成普通业务失败。使用 Polly 8.6.6 时,要检查异常里的 token 是否来自超时策略内部,而不只是看调用方是否把 token 传给了 ExecuteAsync。
超时策略给回调换了一个 token
超时策略需要在期限到达时通知回调停止,所以会创建自己的取消源,将它的 token 放进执行上下文,并把调用方原来的取消信号登记到这个内部取消源上。
回调若使用收到的 token 执行 I/O 或调用 ThrowIfCancellationRequested(),抛出的 OperationCanceledException 可能携带内部 token。8.6.6 恢复上下文中的原 token 后,仍直接返回这个异常结果。因此“调用方已发出取消”与“异常 token 等于调用方 token”并不总是同时成立。
这不是要求回调绕过 Polly、只使用外层捕获的 token。那样可能让策略自己的超时信号到不了真正执行的 I/O,破坏本来的时间限制。
8.7.0 已补上调用方 token 的传播
正式 8.7.0 在 timeout 和 hedging 策略中加入相应处理;8.8.0 的 timeout 实现继续保留它。就超时策略而言,检查顺序仍有区别:策略已经取消、结果是取消异常、且原 token 尚未取消时,会生成 TimeoutRejectedException;其余结果再经过调用方 token 的传播处理。
该传播函数在调用方 token 已取消、结果是取消异常且 token 不同的条件下,创建携带调用方 token 的 OperationCanceledException,并把原异常保留为 InnerException。它不是把全部失败强制改成取消,也没有覆盖任意第三方策略的内部实现。
使用这条版本线时,可采用 8.7.0 或已包含该改动的兼容版本,并检查最终解析的 Polly.Core 及相关集成包。不要只升级一处直接引用,却让运行时仍加载旧版本。
下面是已有异步方法中的处理片段,callerCancellationToken 来自当前操作。DoWorkAsync 代表应用已有的、会响应取消的异步操作:
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(3))
.Build();
try
{
await pipeline.ExecuteAsync(
async token => await DoWorkAsync(token),
callerCancellationToken);
}
catch (TimeoutRejectedException)
{
throw;
}
catch (OperationCanceledException ex)
when (ex.CancellationToken == callerCancellationToken)
{
throw;
}
片段需要 Polly 与 Polly.Timeout 命名空间。两个 catch 都继续传播异常,留给真正负责用户结果的边界决定如何呈现;它们没有把超时或取消变成成功,也没有在失败后自动再发起一次操作。
旧版本的 token 状态判断有竞态边界
暂时保留 8.6.6 时,检查当前操作自己的 IsCancellationRequested 可以识别“调用方现在也处于取消状态”,但不能单独证明它一定是最先发生的原因。真实超时可能先发生,调用方稍后又取消;只按检查时刻的状态给所有异常改名,会丢掉这段顺序。
需要区分两类结果的应用,应记录操作开始、超时事件和外部取消的时间,保留原异常链;不要在通用异常包装器里把任何带已取消 token 的失败都吞成用户取消。依赖版本改变后,也不能仅根据异常携带的 token 反推出所有嵌套策略的首次触发顺序。
回查调用方先取消、策略先超时、正常完成及两者接近同时发生的情况。回调要把收到的 token 继续传给实际 I/O,并正确释放资源。若底层忽略取消,超时策略也不能替应用强制终止它;升级 token 传播逻辑不会替代这项执行契约。


