Flurl.Http 默认不共享连接池,高并发易耗尽端口;应复用 FlurlClient 或全局配置 DefaultHttpClientFactory,并避免 new HttpClient(),推荐使用 IHttpClientFactory 管理生命周期。

Flurl.Http 的 GetAsync 默认不共享连接池,高并发下容易耗尽端口
Flurl.Http 默认为每个 FlurlClient 实例创建独立的 HttpClient,而它内部又默认启用 HttpMessageHandler 的新实例(即非复用)。这意味着每发起一个请求,都可能新建一个底层 TCP 连接,尤其在短生命周期、高频调用场景(如循环发 100 个 GetAsync)中,极易触发 SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full。
解决方法是显式复用同一个 FlurlClient 实例,并配置其 WithSettings 启用连接池:
var client = new FlurlClient()
.WithSettings(s => {
s.HttpClientFactory = new DefaultHttpClientFactory();
// 确保复用底层 HttpClient 实例
});
await "https://api.example.com/data".WithClient(client).GetAsync();
更稳妥的做法是全局注册单例 FlurlClient,避免每次构造:
- 在启动时注册:
FlurlHttp.Configure(c => c.HttpClientFactory = new DefaultHttpClientFactory()); - 或直接复用静态
FlurlClient.Default(注意线程安全,它本身是线程安全的)
HttpClient 必须手动管理生命周期,直接 new HttpClient() 是反模式
很多人写 new HttpClient() 发请求,看似简单,实则埋雷:DNS 变更不生效、TIME_WAIT 连接堆积、SOCKET 耗尽。.NET 官方明确要求 HttpClient 应长期复用(如作为单例或注入 IHttpClientFactory)。
正确姿势只有两种:
- 使用
IHttpClientFactory(推荐,尤其在 ASP.NET Core 中):services.AddHttpClient("myapi", c => { c.BaseAddress = new Uri("https://api.example.com/"); });然后通过IHttpClientFactory.CreateClient("myapi")获取——它自动处理连接池、DNS 刷新、超时隔离 - 手动单例复用(仅限极简控制台程序):
private static readonly HttpClient _sharedClient = new HttpClient();
但需自行配置Timeout、DefaultRequestHeaders等,且无法按命名区分策略
Flurl.Http 的链式语法在并发组合请求时更直观,但掩盖了错误传播细节
比如并发拉取多个用户信息:
var tasks = userIds.Select(id =>
$"https://api.example.com/users/{id}".GetJsonAsync());
var users = await Task.WhenAll(tasks);
这段代码比等价的 HttpClient 版本少写 5–6 行,可读性高。但它默认把所有异常包装成 FlurlHttpException,且 Task.WhenAll 遇到任一失败就整体抛出 AggregateException,原始 HTTP 状态码(如 429、503)需要从 ex.Call.Response.StatusCode 里挖。
而原生 HttpClient 更“透明”:
- 你可以直接检查
HttpResponseMessage.IsSuccessStatusCode - 用
EnsureSuccessStatusCode()或手动switch (res.StatusCode)分流处理 - 对 429 做重试、对 5xx 做降级,逻辑更可控
Flurl.Http 的默认超时是 100 秒,HttpClient 是无穷(TimeSpan.MaxValue)
这个差异在长轮询或大文件下载场景中会暴露问题:Flurl.Http 的 GetAsync 若卡住 100 秒自动中断并抛 TaskCanceledException;而裸 HttpClient 可能无限挂起(除非你显式设 Timeout 或用 CancellationToken)。
调整方式:
- Flurl.Http 全局改:
FlurlHttp.Configure(c => c.Timeout = TimeSpan.FromSeconds(30)); - Flurl.Http 单次改:
url.WithTimeout(30).GetAsync() - HttpClient 必须每次传
CancellationToken或设实例级Timeout(不推荐,因影响所有请求)
真正要注意的是:Flurl.Http 的 Timeout 包含 DNS 解析、连接、发送、接收全过程;而 HttpClient.Timeout 仅作用于整个请求周期,行为一致。但 Flurl 默认值更“保守”,上线前务必核对是否符合业务 SLA。










