新工作入职不久,目前仍然还在适应环境当中,笔者不得不说看别人的源码实在是令人痛苦。所幸前些日子终于将工作流畅地看了一遍,接下来就是熟悉框架技术的阶段了。

也正是在看源码的过程当中,有一个比较明显的用法细节引起了我的注意,我发现一位同事在请求远程Web Api时,虽然使用了 HttpClient 类,但是在用法上似乎有些欠考虑。代码抽象出来就是以下的模样:

using(var client = new HttpClient())
{
//do something
}

我们知道 using 关键字常常和实现了 IDisposable 接口的类型一起使用(如数据库连接和文件流操作),用于释放对象机资源(关于GC回收的相关知识可参考我的另一篇博文《CLR和.Net对象生存周期》),但是对于 HttpClient 这样直接和TCP/IP协议打交道的类型却是未必( HttpClient 继承了 HttpMessageInvoker 类, HttpMessageInvoker 实现了 IDisposable 接口,实现上是比较经典的代理模式),翻看一些国内外的文章都能看到对在 using 关键字中使用 HttpClient 的吐槽。事实是不是真的这样呢,其实只要做一个小实验就可以了。

让我们先试着运行以下代码

class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 10000; i++)
{
using (var client = new HttpClient())
{
var result = client.GetAsync("http://www.baidu.com").Result;
Console.WriteLine(result.StatusCode);
}
} Console.ReadKey();
}
}

不出意外就会提示以下错误:

单纯为了解决问题而言,我们可以通过减小 HttpClientTimeout 属性加快回收速度(修改系统变量可能会引发其他的问题),但实际上,这还是因为 HttpClient 消耗了太多套接字连接的关系。为了验证这个问题,我们可以使用TcpView这个小工具来查看下项目运行时的 TCP 连接数,如果你下载了代码运行后,会发现 TCP 连接和疯狗一样向上猛蹿。虽然还会有套接字回收的现象,但是和增加的速度相比确实是杯水车薪。

所以这时候我们需要换一种写法:

class Program
{
private static readonly HttpClient Client=new HttpClient(); static void Main(string[] args)
{
for (int i = 0; i < 10000; i++)
{ var result = Client.GetAsync("http://www.baidu.com").Result;
Console.WriteLine(result.StatusCode);
} Console.ReadKey();
}
}

更换以上写法后,我们会发现无论我们将循环上限如何调整,也不会出现套接字连接资源不足的情况了,而TCPView的结果也好看得多,甚至如果我们每次都测试传输时间的话,我们会发现单次调用 HttpClient 而言,第二种代码比第一种代码要快得多。其实这很好理解,HttpClient内部维持一个专有的连接池,每个HttpClient实例的请求相互隔绝,加快速度的原因是因为重用了套接字,去除了套接字重新建立连接的过程。这也很好地解释了dudu园长的那一篇博客 《C#中HttpClient使用注意:预热与长连接》中的“预热”说法。盗一张图来说明一下套接字的使用情况。

因此,在使用 HttpClient 时我们知道以下几件小事

  • 将其定义为单例模式(由单独的HttpClient维护连接池)
  • 不要使用using关键字包裹(无效,套接字资源不会跟随释放)
  • 尽量不要额外改变 HttpClient 的一些特殊行为(如上文中的TimeOut)
  • 当你需要配置不同的Http请求时,允许生成并使用多个HttpClient

其实HttpClient还有一种使用隐患,DNS-Bug,这种做法国外也有同僚给出了相应的解释和解决方案,详情请见《Singleton HttpClient? Beware of this serious behaviour and how to fix it

单例模式扩展开来也有很多的说法,根据C#的一些规范,在编程中我推荐三种做法

A. 静态构造器

这种方式适用于如上代码场景,使用静态构造器确保静态字段的实例化。

class Program
{ private static readonly HttpClient Client; static Program()
{
Client=new HttpClient();
} static void Main(string[] args)
{
//do something
}
}

B. HttpClientHelper

单例模式中,经典的双重检查锁定机制。

public static class HttpClientHelper
{ private static readonly object LockObj = new object(); private static HttpClient _client; public static HttpClient HttpClient {
get
{
if (_client == null)
{
lock (LockObj)
{
if (_client == null)
{
_client= new HttpClient();
}
}
} return _client;
}
}
}

C. HttpClientHelper

这是在编程规范中推荐的一种的做法,通过使用静态构造函数能够精确保证Client变量能够在它第一次被使用前被实例化。

当然你也可以直接使用内联的方式进行初始化,这样可以对类型进行性能优化,不过变量的初始化时间就无法进行精准控制了

public sealed class HttpClientHelper
{ private HttpClientHelper(){} public static readonly HttpClient Client; static HttpClientHelper()
{
Client=new HttpClient();
}
}

寄语

多点实践多点总结,为认识更深刻的代码世界而奋斗。

最新文章

  1. 6.C#WinForm基础城市选择器
  2. PYTHON 写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者
  3. cookie的session_id解释
  4. python 最大公约数
  5. Hibernate的映射文件配置
  6. ASP.NET MVC Web API 学习笔记---第一个Web API程序
  7. ==与equals()的区别
  8. Android -- 浮动组建
  9. 理解bashrc和profile[转载]
  10. CATransition的动画效果类型及实现方法--老代码备用参考
  11. Django国际化注意事项
  12. poj3062---输入什么输出什么
  13. POJ 3835 &amp;amp; HDU 3268 Columbus’s bargain(最短路 Spfa)
  14. Android内存优化之OOM
  15. placeholder各种浏览器兼容问题
  16. 如何将网站升级为HTTPS协议?
  17. MySQL Crash Errcode: 28 - No space left on device
  18. (转)regex类(个人理解)
  19. LaLeX数学公式
  20. 梳理:python—同一个类中的方法调用

热门文章

  1. [ 高并发]Java高并发编程系列第二篇--线程同步
  2. .NET面试题系列[8] - 泛型
  3. 游戏服务器菜鸟之C#初探一游戏服务
  4. JavaScript Date对象
  5. 普通程序员如何转向AI方向
  6. seaJs学习笔记2 – seaJs组建库的使用
  7. equals变量在前面或者在后面有什么区别吗?这是一个坑点
  8. CSS常见技巧
  9. 【从零开始学BPM,Day3】自定义表单开发
  10. linux-图形化远程管理协议