httpclient 连接池工具类_C#中HttpClient的使用小结

本文总结了使用HttpClient的最佳实践,包括创建尽可能少的实例,针对性分配HttpClient,正确处理响应内容,使用HttpCompletionOption,分段读取以避免内存问题,以及利用IHttpClientFactory进行管理。此外,文章还探讨了异步方法的处理,如ConfigureAwait(false)的使用,以及并行化处理的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在之前的一周里,我利用业余时间帮人做视频下载工具,花了差不多20小时。期间发生的一些事情令我很不愉快,虽然到底是写好了相关类库,但最终成品是不打算做了。好在这段时间算是把 HttpClient 的相关知识复习了一遍,倒也不算白忙一场。

本文便是对此的总结。

------19.10.31 01.14AM-------

朋友联系我,说之前忙疯了,一直没回我。

所以我不生气了,打算认真把这东西做完。

但前面的字就不删了。当作某人的黑历史。

老子的心情还是很不愉悦很不愉悦不愉悦。

----------------------

============ HttpClient 部分 ============

1、创建尽可能少的HttpClient实例

以下代码明显是错误的:

using (var client = new HttpClient()) {
    
     /*todo*/ }

官方文档的 Remark 部分对此有详细的介绍。这么做的后果是频繁调用将耗尽socket数量,造成 SocketException 。

正确的做法是创建尽可能少的实例,将针对某一类请求的 HttpClient 放入类的静态变量中,甚至放入静态工具类中。

2、针对性地分配HttpClient实例

基于第一条,显然整个程序集只使用一个 HttpClient 实例是最理想的情况,例如实现一个只需要处理知乎网站访问请求的程序。

然而很多情况下,一个程序集可能会提供多个网站客户端的实现,此时应当针对性地为每个实现分配一个 HttpClient 实例:

因为 HttpClient 只有几个异步方法是线程安全的,其他成员都非线程安全。

必须采用针对性分配的内容是那些必须通过 HttpClientHandler (在.NET Core中应该使用SocketsHttpHandler)及其派生类来设置的内容。

比如 重定向 ,代理 等等。这种情况下,应当使用 HttpClient(HttpMessageHandler) 构造函数初始化实例。

一个特殊的情况是记录cookie。

虽然我直接使用 HttpClientHandler.UseCookies 来使 HttpClient 能够记录cookie,但另一种常见的做法是通过请求获取的 HttpResponseMessage 与 HttpRequestMessage 来手动记录cookie,并通过 HttpClient.SendAsync 方法发送请求。

这样的好处是只需要一个 HttpClient 实例,缺点在于但一旦需要记录更多的网站cookie,那么就需要很多额外的操作。

3、除非确定数据小于83kb,否则应当用流来读取响应内容

首先让我们来关注两个数字:85000,81920。

一个对象,如果它的大小大于85000字节,那么它将会分配在 大型对象堆(LOH) 上。分配LOH具有极大的开销,所以如果数据大于这个值的情况下,依然直接使用 HttpContent.ReadAsByteArrayAsync 或 HttpContent.ReadAsStringAsync 方法来获取数据,则将会造成巨大的性能损失。

正确的做法是使用 HttpContent.CopyToAsync 方法,或者先通过 HttpContent.ReadAsStreamAsync 方法获取流,然后再进行相关操作。

3.5、缓冲区池化。

CopyToAsync 方法会使用 stream 默认的缓冲区。这个缓冲区的大小就是之前提到的81920。

扩大缓冲区的值会提高IO的效率,而缓冲区的复用可以减少内存分配的开销,避免造成内存碎片化,所以我建议使用缓冲区池。

利用 ConcurrentBag<T> 可以在几行代码之内实现缓冲区池:

private static class BytesPool
{
    
    
    private static readonly ConcurrentDictionary<int, ConcurrentBag<byte[]>> _BytesPool = new ConcurrentDictionary<int, ConcurrentBag<byte[]>>();

    public static byte[] Rent(int size) => _BytesPool.GetOrAdd(size, new ConcurrentBag<byte[]>()).TryTake(out var bytes) ? bytes : new byte[size];

    public static void Return(byte[] bytes) => _BytesPool.GetOrAdd(bytes.Length, new ConcurrentBag<byte[]>()).Add(bytes);
}

除此之外,我还为Stream 写了一个扩展方法:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值