Polly超时策略单独使用未触发,与重试策略结合正常的问题排查
我尝试让Polly超时策略生效,但单独使用时无法触发,与重试策略结合使用时却能正常工作。策略定义如下:
声明:此为纯测试代码,忽略代码异味
var httpRetryPolicy = Policy.Handle<TimeoutRejectedException>() .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode) .WaitAndRetryAsync( 5, retryAttempt => TimeSpan.FromSeconds(1), (_, _, retryAttempt, _) => Debug.WriteLine($"Retrying ({retryAttempt})") ); var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>( 4, TimeoutStrategy.Optimistic, (_, _, _, _) => { Debug.WriteLine("Timeout"); return Task.CompletedTask; } );
PollyTestController中的策略测试端点
[HttpGet("{policyKey}/{delay}")] public async Task<IActionResult> RunPolicyTest(string policyKey, int delay) { AsyncPolicyWrap<HttpResponseMessage> policy = null; if(policyKey == "T") policy = Policy.WrapAsync(timeoutPolicy, Policy.NoOpAsync<HttpResponseMessage>()); else if(policyKey == "TR") policy = Policy.WrapAsync(timeoutPolicy, httpRetryPolicy); HttpClient httpClient = new HttpClient(); var url = $"http://localhost:5000/test/{delay}"; try { var response = await policy.ExecuteAsync(() => httpClient.GetAsync(url)); return Ok(await response.Content.ReadAsStringAsync()); } catch (Exception ex) { return BadRequest(ex.Message); } }
TestController中的测试端点
[HttpGet("{delay}")] public IActionResult GetWithDelay(int delay) { if (delay == 10) return NotFound("Not found"); Thread.Sleep(delay*1000); return Ok("Success"); }
场景1 - 调用 http://localhost:5000/pollytest/T/6
此调用会选择“T”策略(仅超时)执行对http://localhost:5000/test/6的请求。根据代码,该响应会延迟6秒,而策略超时时间为4秒,本应抛出TimeoutRejectedException,但实际情况是请求挂起6秒后返回“Success”响应,输出窗口无任何内容。
场景2 - 调用 http://localhost:5000/pollytest/TR/10
此调用会选择“TR”策略(超时包装重试)执行对http://localhost:5000/test/10的请求。由于delay值为10,端点会返回404,因此重试策略会尝试5次,每次间隔1秒。但此次超时策略在第4次尝试后触发,第5次尝试未执行,输出窗口显示:
Retrying (1) Retrying (2) Retrying (3) Retrying (4) Timeout
响应内容为“The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.”
我忽略了什么?为什么场景1中的超时策略未触发?
问题原因
你使用的是**TimeoutStrategy.Optimistic(乐观超时),这种超时策略的生效前提是:被执行的异步委托必须支持协作取消**——也就是委托内部要监听CancellationToken,并在收到取消信号时主动终止执行。
在场景1中,你调用policy.ExecuteAsync(() => httpClient.GetAsync(url))时,没有传入Polly超时策略生成的CancellationToken。HttpClient.GetAsync的无参重载不会响应外部取消请求,所以即使超时策略触发了取消信号,HTTP请求仍会继续执行直到完成,导致超时逻辑完全不生效。
而场景2中,重试策略内部会自动传递CancellationToken,当超时策略触发时,重试流程会响应这个取消信号,终止后续重试并抛出TimeoutRejectedException,因此超时策略正常工作。
修复方案
修改场景1中执行委托的代码,传入Polly提供的取消令牌:
将原代码中的:
var response = await policy.ExecuteAsync(() => httpClient.GetAsync(url));
改为:
var response = await policy.ExecuteAsync(ct => httpClient.GetAsync(url, ct));
这样HttpClient.GetAsync会监听超时策略生成的CancellationToken,当超时时间到达时,请求会被主动取消,超时策略的回调和异常也会正常触发。
另外补充:如果无法修改委托以支持取消,可以考虑改用**TimeoutStrategy.Pessimistic(悲观超时)**。它会在独立线程中执行委托,超时后直接终止线程,但这种方式有额外线程开销,还可能引发资源泄漏问题,需谨慎使用。
内容的提问来源于stack exchange,提问作者Sam

