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

Polly与HttpClient集成出现请求无限重试挂起问题求助

问题诊断与修复方案

从你的代码和日志来看,请求挂起以及日志顺序异常主要由几个关键问题导致,下面逐一分析并给出修复方法:

1. 同步阻塞引发的死锁(最可能的挂起原因)

在处理响应内容时,你使用了同步阻塞的.Result:

string responseContent = response.Content.ReadAsStringAsync().Result;

在异步方法中调用.Result或.Wait()会直接阻塞当前线程,尤其是在存在同步上下文(比如ASP.NET、WinForms/WPF)的环境下,极易触发死锁——异步操作完成后需要回到原上下文,但原线程正被阻塞等待结果,最终导致整个请求卡住无法继续。

修复方法:替换为异步等待await,并配合ConfigureAwait(false)规避上下文切换风险:

string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

2. 频繁创建HttpClient导致套接字耗尽

你在每次调用callAsync时都新建HttpClient实例:

httpcli = new HttpClient();

HttpClient的设计定位是长生命周期复用对象,频繁创建会导致系统中积累大量处于TIME_WAIT状态的套接字,最终耗尽可用端口,新请求无法发起,表现为请求挂起。

修复方法:将HttpClient设置为静态成员全局复用,初始化逻辑放在静态构造函数中:

// 静态实例,全局复用
private static readonly HttpClient _httpClient = new HttpClient();
private static readonly int _maxRetryAttempts = 3;
private static readonly TimeSpan _pauseBetweenFailures = TimeSpan.FromSeconds(10);

static YourClassName()
{
    // 初始化安全协议
    ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
    // 预设授权头
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ConfigurationManager.AppSettings["XXXAuth"].ToString());
    // 移除ConnectionClose配置,让HttpClient自动管理连接池
    // _httpClient.DefaultRequestHeaders.ConnectionClose = true;
}

3. 重试日志顺序异常的原因

你的日志显示重试次数是3 → 2 → 1,这是因为Polly的WaitAndRetryAsync回调中,retryCount代表即将执行的重试次数,而非已完成的次数。比如设置maxRetryAttempts = 3时:

  • 第一次失败后,准备执行第1次重试,retryCount=1
  • 第二次失败后,准备执行第2次重试,retryCount=2
  • 第三次失败后,准备执行第3次重试,retryCount=3

日志顺序错乱大概率是异步日志写入的时序问题,若要明确重试进度,可以调整日志描述:

.WaitAndRetryAsync(_maxRetryAttempts, j => _pauseBetweenFailures, (result, timeSpan, retryCount, context) => {
    Logger.WriteInfo($"Request failed with {result.Result?.StatusCode ?? HttpStatusCode.InternalServerError}. About to retry {retryCount}/{_maxRetryAttempts}. Waiting {timeSpan}...");
});

4. 不必要的ConnectionClose设置

你设置了httpcli.DefaultRequestHeaders.ConnectionClose = true;,这会强制每次请求后关闭TCP连接,破坏HttpClient的连接池机制,增加TCP握手开销,同时可能加剧套接字耗尽问题,建议直接移除该配置。

修复后的完整代码示例

// 静态HttpClient实例,全局复用
private static readonly HttpClient _httpClient = new HttpClient();
private static readonly int _maxRetryAttempts = 3;
private static readonly TimeSpan _pauseBetweenFailures = TimeSpan.FromSeconds(10);

static YourClassName()
{
    // 初始化安全协议
    ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
    // 设置默认Authorization头
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ConfigurationManager.AppSettings["XXXAuth"].ToString());
}

public static async Task<ResponseWs> callAsync(int IdHeader) {
    ResponseWs w = new ResponseWs();
    w.retCode = 1;
    w.Message = "Errore Generico";
    
    getInbound(IdHeader, out int i, out string jsonObject, out string orderNum);
    string url = String.Format("{0}charge?orderNum={1}", ConfigurationManager.AppSettings["XXXUrl"], orderNum);
    Logger.WriteInfo(url);
    Logger.WriteInfo(jsonObject);

    var retryPolicy = Policy
        .Handle<HttpRequestException>()
        .OrResult<HttpResponseMessage>(r => r.StatusCode != HttpStatusCode.OK)
        .WaitAndRetryAsync(_maxRetryAttempts, j => _pauseBetweenFailures, (result, timeSpan, retryCount, context) => {
            Logger.WriteInfo($"Request failed with {result.Result?.StatusCode ?? HttpStatusCode.InternalServerError}. About to retry {retryCount}/{_maxRetryAttempts}. Waiting {timeSpan}...");
        });

    try {
        var response = await retryPolicy.ExecuteAsync(async () => {
            var cont = new StringContent(jsonObject, Encoding.UTF8, "application/json");
            // 使用构建好的目标URL,替换硬编码的测试地址
            var post = await _httpClient.PostAsync(url, cont).ConfigureAwait(false);
            return post;
        });

        if (response.IsSuccessStatusCode) {
            string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            XXXError sperr = JsonConvert.DeserializeObject<XXXError>(responseContent);
            if (sperr.status.Equals("OK", StringComparison.OrdinalIgnoreCase)) {
                Logger.WriteInfo("OK");
                w.retCode = 0;
                w.Message = "OK";
                return w;
            } else {
                Logger.WriteError(responseContent);
                Logger.WriteError(sperr.message);
                w.retCode = 2;
                w.Message = sperr.message;
                return w;
            }
        } else {
            Logger.WriteError($"Errore: {response.StatusCode}");
            string errorContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            Logger.WriteError(errorContent);
            w.retCode = 1;
            w.Message = "Errore Chiamata MW";
            return w;
        }
    } catch (HttpRequestException e) {
        Logger.WriteError(e.Message);
        if (e.InnerException != null)
            Logger.WriteError(e.InnerException.Message);
        w.retCode = 1;
        w.Message = "Errore Chiamata MW";
        return w;
    } catch (Exception ex) {
        Logger.WriteError(ex.Message);
        if (ex.InnerException != null)
            Logger.WriteError(ex.InnerException.Message);
        w.retCode = 1;
        w.Message = "Errore Chiamata MW";
        return w;
    }
}

额外注意事项

  • 确保maxRetryAttempts和pauseBetweenFailures的值合理,避免过度重试给目标服务造成压力;
  • 即使是非成功状态码,也建议用await读取响应内容,避免同步阻塞;
  • 移除了AggregateException的捕获,因为Polly在重试失败后,若为结果失败(如返回400)会直接返回失败响应,若为异常失败则抛出最后一次的异常,无需单独捕获AggregateException。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 00:33:28