.NET 6多异步任务共用HttpClient遇远程主机强制关闭连接问题
问题:多后台Worker共用HttpClient时出现连接被强制关闭错误
场景说明
使用.NET 6开发,应用通过多个后台Worker共用同一个HttpClient异步发起请求,偶尔会抛出连接被强制关闭的错误;但仅用单个Task执行时无此问题。
错误日志
System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.. ---> System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host. --- End of inner exception stack trace --- at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken) at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource<System.Int32>.GetResult(Int16 token) at System.Net.Security.SslStream.EnsureFullTlsFrameAsync[TIOAdapter](TIOAdapter adapter) at System.Net.Security.SslStream.ReadAsyncInternal[TIOAdapter](TIOAdapter adapter, Memory`1 buffer) at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
已尝试的无效操作
设置ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12,问题未解决。
当前HttpClient配置
serviceCollection.AddHttpClient(Queries.Sne.Serpro.Api.Infracoes.InfracaoQuery.CLIENT_KEY, (provider, client) => { client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); client.DefaultRequestHeaders.Add("Connection", "keep-alive"); client.BaseAddress = new Uri("https://fake-link/"); });
解决方案
- 移除手动添加的
Connection: keep-alive请求头:HttpClient默认已启用连接池和长连接机制,手动设置该头会干扰.NET对连接的自动管理,容易引发服务器端连接冲突。 - 调整HttpClient连接池参数:通过
SocketsHttpHandler配置连接池的最大并发连接数、空闲连接超时等,匹配服务器端的连接限制:
serviceCollection.AddHttpClient(Queries.Sne.Serpro.Api.Infracoes.InfracaoQuery.CLIENT_KEY, (provider, client) => { client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); client.BaseAddress = new Uri("https://fake-link/"); }).ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { MaxConnectionsPerServer = 100, // 根据实际并发需求调整 PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5), // 建议与服务器的长连接超时保持一致 PooledConnectionLifetime = TimeSpan.FromMinutes(10) // 定期回收旧连接,避免因服务器端连接过期引发错误 });
- 添加请求重试策略:针对连接被强制关闭的异常,实现指数退避重试,可结合Polly库实现:
using Polly; using Polly.Retry; // 定义重试策略 AsyncRetryPolicy retryPolicy = Policy .Handle<HttpRequestException>() .Or<SocketException>(ex => ex.SocketErrorCode == System.Net.Sockets.SocketError.ConnectionReset) .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // 将策略绑定到HttpClient serviceCollection.AddHttpClient(Queries.Sne.Serpro.Api.Infracoes.InfracaoQuery.CLIENT_KEY, (provider, client) => { client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); client.BaseAddress = new Uri("https://fake-link/"); }).AddPolicyHandler(retryPolicy);
- 确认服务器端连接限制:部分服务器会对单个客户端的并发连接数、长连接时长做限制,多Worker并发时可能触发该限制,需联系服务端运维确认配置,或调整客户端的并发请求量。
内容的提问来源于stack exchange,提问作者Guilherme Bley
相关产品推荐
相关产品推荐

