C# HttpClientFactory使用方法 C#如何正确管理HttpClient实例

来源:这里教程网 时间:2026-02-21 17:40:24 作者:

为什么不能每次 new HttpClient()?

直接

new HttpClient()
会导致端口耗尽、DNS 缓存失效、连接复用失败等问题。.NET 的
HttpClient
是线程安全且设计为长期复用的,频繁创建销毁会触发底层
Socket
资源泄漏(尤其在高并发下),错误日志里常出现
SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full

根本原因不是

HttpClient
本身没 Dispose,而是它内部的
HttpMessageHandler
(默认是
HttpClientHandler
)持有连接池和 DNS 缓存,过早释放会打断复用链路。

HttpClientFactory 是怎么解决这个问题的?

HttpClientFactory
不是帮你“创建 HttpClient”,而是管理背后的
HttpMessageHandler
生命周期,并按需提供轻量级、短命的
HttpClient
实例(每个实例只持有一个 handler 引用,不拥有 handler)。handler 由工厂统一维护,默认 2 分钟空闲后回收,支持 DNS 刷新、连接池自动调优。

使用时你拿到的是“借用”的

HttpClient
,不用
Dispose
,也不该缓存它——下次
GetClient("api")
拿到的可能是新实例,但底层 handler 很可能复用。

关键点:

AddHttpClient
注册时传入的配置(如 BaseAddress、Timeout、DefaultRequestHeaders)只影响新生成的 client 实例,不影响 handler
handler 级别配置(如
MaxConnectionsPerServer
)必须通过
ConfigurePrimaryHttpMessageHandler
或自定义
IHttpMessageHandlerBuilderFilter
设置
命名客户端(如
services.AddHttpClient("github", c => c.BaseAddress = new Uri("https://api.github.com/"))
)适合多服务场景,类型化客户端更利于单元测试

类型化客户端(Typed Client)怎么写才对?

类型化客户端本质是一个普通类,构造函数接收

IHttpClientFactory
或直接注入
HttpClient
(推荐后者,更简洁)。它不是单例,每次解析都新建,但内部的
HttpClient
由工厂供应,生命周期解耦。

示例:

public class GitHubService
{
    private readonly HttpClient _httpClient;
    public GitHubService(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("myapp/1.0");
    }
    public async Task<string> GetLatestRelease(string repo)
    {
        var response = await _httpClient.GetAsync($"/repos/{repo}/releases/latest");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

注册方式:

services.AddHttpClient<GitHubService>(client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.Timeout = TimeSpan.FromSeconds(10);
});

注意:

AddHttpClient<t></t>
注册的是服务类型,不是
HttpClient
T
构造函数中注入的
HttpClient
已被工厂包装,无需再处理重试或超时逻辑(这些应放在 factory 配置或 Polly 策略里)。

超时、重试、认证这些怎么加?

超时建议设在

HttpClient
实例级别(通过
AddHttpClient
配置),而不是在每次请求时用
CancellationToken
控制——后者只中断当前请求,不释放连接;前者能触发 handler 层连接清理。

重试和断路器必须用

IHttpClientBuilder.AddPolicyHandler
(基于 Polly),例如:

services.AddHttpClient<GitHubService>()
    .SetHandlerLifetime(TimeSpan.FromMinutes(5))
    .AddPolicyHandler(Policy.Handle<HttpRequestException>()
        .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));

认证头推荐用

AddHttpMessageHandler
注入自定义
DelegatingHandler
,避免每次手动设置
DefaultRequestHeaders

Token 过期需刷新?不要在 handler 里同步调用
GetAccessTokenAsync
,改用
IAsyncAuthorizationHeaderProvider
模式或配合
ITokenProvider
+
RefreshTokenHandler
Basic Auth 明文密码?把凭据存在
IConfiguration
ISecretsManager
,handler 中读取后编码
Bearer Token 需动态更新?handler 中检查
DateTimeOffset.Now ,过期则异步刷新并更新 <code>Authorization

handler 的

SendAsync
是同步入口,所有 await 必须显式声明,否则会阻塞连接池线程。

相关推荐