新工做入职不久,目前仍然还在适应环境当中,笔者不得不说看别人的源码实在是使人痛苦。所幸前些日子终于将工做流畅地看了一遍,接下来就是熟悉框架技术的阶段了。html
也正是在看源码的过程中,有一个比较明显的用法细节引发了个人注意,我发现一位同事在请求远程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(); } }
不出意外就会提示如下错误:框架
单纯为了解决问题而言,咱们能够经过减少 HttpClient
的 Timeout
属性加快回收速度(修改系统变量可能会引起其余的问题),但实际上,这仍是由于 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
的一些特殊行为(如上文中的TimeOut)其实HttpClient还有一种使用隐患,DNS-Bug,这种作法国外也有同僚给出了相应的解释和解决方案,详情请见《Singleton HttpClient? Beware of this serious behaviour and how to fix it》优化
单例模式扩展开来也有不少的说法,根据C#的一些规范,在编程中我推荐三种作法
这种方式适用于如上代码场景,使用静态构造器确保静态字段的实例化。
class Program { private static readonly HttpClient Client; static Program() { Client=new HttpClient(); } static void Main(string[] args) { //do something } }
单例模式中,经典的双重检查锁定机制。
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; } } }
这是在编程规范中推荐的一种的作法,经过使用静态构造函数可以精确保证Client变量可以在它第一次被使用前被实例化。
固然你也能够直接使用内联的方式进行初始化,这样能够对类型进行性能优化,不过变量的初始化时间就没法进行精准控制了
public sealed class HttpClientHelper { private HttpClientHelper(){} public static readonly HttpClient Client; static HttpClientHelper() { Client=new HttpClient(); } }
多点实践多点总结,为认识更深入的代码世界而奋斗。