HttpClient 必须全局复用,禁用每次 new;超时控制须用 CancellationToken 而非修改 Timeout 属性;应通过 IHttpClientFactory 注册并管理生命周期,避免手动 Dispose。

HttpClient 实例必须复用,不能每次 new
在高并发场景下,频繁 new HttpClient() 会导致端口耗尽、DNS 缓存失效、连接池无法复用,最终抛出 SocketException: Too many open files 或 HttpRequestException: Connection refused。.NET 的 HttpClient 是线程安全的,设计初衷就是长期复用——它内部管理着 HttpMessageHandler(如 HttpClientHandler)和底层连接池。
错误写法:
public string Get(string url)
{
using var client = new HttpClient(); // ❌ 每次都新建,灾难性
return client.GetStringAsync(url).Result;
}
正确做法:
- 全局单例:在 .NET Core 6+ 中推荐注册为
Singleton服务,或直接定义static readonly HttpClient - 避免在
using中包裹HttpClient,否则会提前释放HttpMessageHandler,破坏连接复用 - 若需不同配置(如超时、证书),应复用
HttpClientHandler实例,而非HttpClient实例
不要手动设置 HttpClient.Timeout 用于单请求控制
HttpClient.Timeout 是实例级属性,修改它会影响后续所有请求,且不是线程安全的。高并发下多个线程同时改 Timeout 会引发不可预测行为,还可能覆盖彼此的设置。
正确做法是使用 CancellationToken 控制单次请求超时:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
var response = await client.GetAsync("https://api.example.com", cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
// 超时处理
}
-
HttpClient.Timeout应只在初始化时设一次(如 100 秒),作为兜底值 - 单请求超时必须走
CancellationToken,这是唯一安全、可取消、可并发的方案 - 注意:不要把同一个
CancellationTokenSource复用于多个并发请求,应每个请求新建或从池中获取
自定义 HttpClientHandler 时务必启用连接池并设合理参数
默认 HttpClientHandler 的连接池行为在高并发下常被忽视。例如 MaxConnectionsPerServer 默认是 int.MaxValue(.NET 5+),但旧版本(.NET Framework / .NET Core 2.x)默认仅 2,极易成为瓶颈;AutomaticDecompression 若未开启,压缩响应体将无法自动解压;UseProxy 和 ServerCertificateCustomValidationCallback 若配置不当,也会引入延迟或 TLS 握手失败。
推荐初始化方式:
var handler = new HttpClientHandler
{
MaxConnectionsPerServer = 100,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
UseProxy = false,
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
var client = new HttpClient(handler);
-
MaxConnectionsPerServer需根据目标域名数量和 QPS 估算,通常 50–200 是较安全起点 - 禁用代理(
UseProxy = false)可省去 DNS 查询和代理协商开销 - 自定义证书验证回调必须明确写出,不能依赖默认行为(尤其在容器或非标准 TLS 环境中)
- 若使用
SocketsHttpHandler(.NET Core 2.1+ 默认),还可进一步调优IdleConnectionTimeout和PooledConnectionLifetime
DI 容器中注册 HttpClient 时别漏掉 IHttpClientFactory
直接注册 HttpClient 单例看似简单,但丢失了生命周期管理、命名客户端、策略注入(如 Polly 重试)、日志与指标集成等关键能力。.NET Core 内置的 IHttpClientFactory 不是“创建 HttpClient 的工厂”,而是“管理 HttpClientHandler 生命周期 + 提供策略扩展”的中枢。
注册方式(Startup.cs 或 Program.cs):
services.AddHttpClient("github", client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.UserAgent.ParseAdd("my-app/1.0");
})
.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(100))); // Polly
- 命名客户端(如
"github")让不同业务模块隔离配置,避免互相干扰 -
AddHttpClient注册的是逻辑客户端,底层仍复用有限的HttpMessageHandler实例池 - 不传名字的
AddHttpClient也适用,但必须配合() IServiceProvider获取,不能直接 new - 切勿在 DI 中注册
HttpClient类型本身——这会让框架误以为你要手动管理它的生命周期
最常被忽略的一点:即使用了 IHttpClientFactory,如果在代码里又对返回的 HttpClient 做 using 或手动 Dispose,依然会提前终结底层 handler,导致连接池失效。工厂返回的 HttpClient 实例本就应视为“即取即用、无需释放”的轻量对象。










