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

.NET 6 Windows服务运行24小时后HttpClient调用持续超时求助

.NET 6 Windows服务24小时后API调用超时问题排查建议

问题背景

.NET 6.0 Windows服务每隔几秒向API发送数据,运行约24小时后持续出现超时异常,仅能通过重启服务解决。已配置PooledConnectionLifetime为2分钟、自定义超时,三个API各有独立静态HttpClient实例。

现有API调用代码

private static HttpClient client = new HttpClient(); 

public static void SetTimeout(int timeout, int poolConnectionLifeTime)
{
    var handler = new SocketsHttpHandler
    {
        PooledConnectionLifetime = TimeSpan.FromMinutes(poolConnectionLifeTime) 
    };
    client = new HttpClient(handler);
    client.Timeout = TimeSpan.FromSeconds(timeout);
    TokenRetriever.Log.Info("Auth API http client initialised");
}      

public async Task<string> ExecuteGetAndReturnResponse(Dictionary<string, string> requestParams, string url)
{
    string responseReturned = string.Empty;

    try
    {
        using (var response = await client.PostAsync(url, new FormUrlEncodedContent(requestParams)))
        {
            if (response.IsSuccessStatusCode)
            {
                using (var stream = await response.Content.ReadAsStreamAsync())
                {
                    using (var streamReader = new StreamReader(stream))
                    {
                        responseReturned = await streamReader.ReadToEndAsync();
                    }
                }
            }
            else
            {
                responseReturned = $"ERROR: - {response.StatusCode} - {response.ReasonPhrase}";
                TokenRetriever.Log.Info($"Response returned {responseReturned}");
            }
        }
    }
    catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
    {
       TokenRetriever.Log.Error("Fetching token Timed out: " + ex.Message);
    }
    catch (TaskCanceledException ex)
    {
        // Handle cancellation.
       TokenRetriever.Log.Error("Task Cancellation fetching token error: " + ex.Message);
    }

    return responseReturned;
}

解决方案建议

  • 修复静态HttpClient初始化的线程安全问题:当前SetTimeout直接替换静态client实例,多线程场景下可能出现竞态条件,导致部分请求使用未正确配置的旧实例。建议用Lazy<HttpClient>实现线程安全的惰性初始化:
    private static readonly Lazy<HttpClient> _client = new Lazy<HttpClient>(() =>
    {
        var handler = new SocketsHttpHandler
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(2)
        };
        var client = new HttpClient(handler);
        client.Timeout = TimeSpan.FromSeconds(30);
        TokenRetriever.Log.Info("Auth API http client initialised");
        return client;
    });
    
    private static HttpClient Client => _client.Value;
    
  • 补充连接池相关配置:除PooledConnectionLifetime外,设置PooledConnectionIdleTimeout(如1分钟)避免闲置连接长期占用;调整MaxConnectionsPerServer为合理值,防止连接池耗尽。
  • 添加重试机制:针对超时、服务端错误实现指数退避重试,可使用Polly库简化逻辑,避免单次超时引发持续失败:
    var retryPolicy = Policy
        .Handle<TaskCanceledException>(ex => ex.InnerException is TimeoutException)
        .Or<HttpRequestException>()
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
    
    var response = await retryPolicy.ExecuteAsync(async () => 
        await Client.PostAsync(url, new FormUrlEncodedContent(requestParams)));
    
  • 处理DNS缓存问题:.NET HttpClient默认缓存DNS解析结果,若API IP变更会导致旧IP失效。在SocketsHttpHandler中配置DnsRefreshTimeout(如5分钟)强制刷新DNS:
    var handler = new SocketsHttpHandler
    {
        PooledConnectionLifetime = TimeSpan.FromMinutes(2),
        DnsRefreshTimeout = TimeSpan.FromMinutes(5)
    };
    
  • 增强日志维度:记录请求ID、连接耗时、活跃连接数,超时异常时补充请求上下文信息,帮助定位是连接池、API端还是网络问题。
  • 改用IHttpClientFactory管理实例:依赖注入方式的IHttpClientFactory会自动处理连接池和实例生命周期,避免手动管理静态HttpClient的潜在问题,更适合长期运行的服务场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 18:05:26