You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Polly重试策略仍抛出HttpClient.Timeout异常且不重试?

HttpClient超时异常无法触发Polly重试的问题排查与解决

问题描述

配置了Polly重试策略处理HttpClient的PostAsync请求,但遇到HttpClient.Timeout异常时,策略不执行重试直接抛出异常,寻求原因及解决办法。

HttpClient配置

_httpClientHandler = new HttpClientHandler()
{
    UseCookies = true,
    CookieContainer = new CookieContainer(),
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
_httpClient = new HttpClient(_httpClientHandler) { Timeout = TimeSpan.FromSeconds(10) };

POST请求及Polly策略代码

private async Task PreparePayloadByDate(DateTime date)
{
    // Define the retry policy with exponential backoff
    var retryPolicy = Policy<HttpResponseMessage>.Handle<HttpRequestException>()
        .Or<TaskCanceledException>()
        .OrResult(r => !r.IsSuccessStatusCode)
        .WaitAndRetryForeverAsync(
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
            onRetry: (exception, retryCount, timeSpan) =>
            {
                _logger.Log(LogEnums.Error, $"Attempt {retryCount} failed. Retrying in {timeSpan} seconds...");
            });
    var stringPayload = JsonConvert.SerializeObject(new { key = "days", options = date.ToString("yyyy-MM-dd") });
    var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
    var r1 = await retryPolicy.ExecuteAsync(() =>
        _httpClient.PostAsync($"{_moduleConfig.Endpoint}/options/selected", httpContent));
}

补充信息

  • 曾尝试用Polly的Timeout策略替代HttpClient内置超时,问题依旧。
  • 重试策略已包含在try-catch块中,抛出的异常信息为:The request was canceled due to the configured HttpClient.Timeout of 15 seconds elapsing.,堆栈跟踪指向at System.Net.Http.HttpClient.HandleFailure(~)。
  • 即使将HttpClient超时设置为3分钟、10分钟,请求仍持续超时,怀疑目标URL存在限流,但浏览器中无法复现该问题。

原因分析

  1. HttpContent复用失效:代码中复用了同一个StringContent对象,而HttpContent在首次请求发送后会被自动释放,后续重试时该对象已处于处置状态,直接抛出异常导致重试逻辑中断。
  2. 异常捕获范围不精准:.NET 5+版本中,HttpClient超时抛出的TaskCanceledException通常会包含TimeoutException作为内部异常,当前策略仅泛泛捕获TaskCanceledException,可能因异常包装逻辑导致识别失效。
  3. Polly策略组合错误:若使用Polly Timeout策略,未通过PolicyWrap正确组合重试与超时策略,会导致超时异常无法被重试策略捕获。

解决办法

1. 修复HttpContent复用问题

每次重试时重新创建StringContent对象,避免复用已处置的内容:

var r1 = await retryPolicy.ExecuteAsync(async () =>
{
    var stringPayload = JsonConvert.SerializeObject(new { key = "days", options = date.ToString("yyyy-MM-dd") });
    var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
    return await _httpClient.PostAsync($"{_moduleConfig.Endpoint}/options/selected", httpContent);
});

2. 精准捕获超时相关异常

调整Polly策略,明确捕获由超时引发的TaskCanceledException:

var retryPolicy = Policy<HttpResponseMessage>
    .Handle<HttpRequestException>()
    .Or<TaskCanceledException>(ex => ex.InnerException is TimeoutException)
    .OrResult(r => !r.IsSuccessStatusCode)
    .WaitAndRetryForeverAsync(
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
        onRetry: (result, retryCount, timeSpan) =>
        {
            var errorMsg = result.Exception != null 
                ? $"exception: {result.Exception.Message}" 
                : $"status code: {result.Result.StatusCode}";
            _logger.Log(LogEnums.Error, $"Attempt {retryCount} failed due to {errorMsg}. Retrying in {timeSpan} seconds...");
        });

3. 正确组合Polly超时与重试策略

若使用Polly Timeout,需通过PolicyWrap为每个重试请求配置独立超时:

// 单请求超时策略
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));
// 重试策略
var retryPolicy = Policy<HttpResponseMessage>
    .Handle<HttpRequestException>()
    .Or<TaskCanceledException>(ex => ex.InnerException is TimeoutException)
    .OrResult(r => !r.IsSuccessStatusCode)
    .WaitAndRetryForeverAsync(
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
        onRetry: (result, retryCount, timeSpan) =>
        {
            // 日志逻辑
        });
// 组合策略:先执行超时控制,再触发重试
var policyWrap = Policy.WrapAsync(retryPolicy, timeoutPolicy);

// 执行请求
var r1 = await policyWrap.ExecuteAsync(async () =>
{
    var stringPayload = JsonConvert.SerializeObject(new { key = "days", options = date.ToString("yyyy-MM-dd") });
    var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
    return await _httpClient.PostAsync($"{_moduleConfig.Endpoint}/options/selected", httpContent);
});

4. 排查目标服务限流差异

对比浏览器与HttpClient的请求头(如User-Agent、Cookie、Referer等),确保请求参数一致;若怀疑限流,可在重试逻辑中读取响应头的Retry-After字段,调整重试间隔以符合服务要求。

内容的提问来源于stack exchange,提问作者Jamie Head

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:26:06