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<User>());
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。
