DotNetCore深入了解之HttpClientFactory类详解
当需要向某特定url地址发送http请求并得到相应响应时,通常会用到httpclient类。该类包含了众多有用的方法,可以满足绝大多数的需求。但是如果对其使用不当时,可能会出现意想不到的事情。
using(var client = new httpclient())
对象所占用资源应该确保及时被释放掉,但是,对于网络连接而言,这是错误的。
原因有二,网络连接是需要耗费一定时间的,频繁开启与关闭连接,性能会受影响;再者,开启网络连接时会占用底层socket资源,但在httpclient调用其本身的dispose方法时,并不能立刻释放该资源,这意味着你的程序可能会因为耗尽连接资源而产生预期之外的异常。
所以比较好的解决方法是延长httpclient对象的使用寿命,比如对其建一个静态的对象:
private static httpclient client = new httpclient();
但从程序员的角度来看,这样的代码或许不够优雅。
所以在.net core 2.1中引入了新的httpclientfactory类。
它的用法很简单,首先是对其进行ioc的注册:
public void configureservices(iservicecollection services) { services.addhttpclient(); services.addmvc(); }
然后通过ihttpclientfactory创建一个httpclient对象,之后的操作如旧,但不需要担心其内部资源的释放:
public class lzzdemocontroller : controller { ihttpclientfactory _httpclientfactory; public lzzdemocontroller(ihttpclientfactory httpclientfactory) { _httpclientfactory = httpclientfactory; } public iactionresult index() { var client = _httpclientfactory.createclient(); var result = client.getstringasync("http://myurl/"); return view(); } }
addhttpclient的源码:
public static iservicecollection addhttpclient(this iservicecollection services) { if (services == null) { throw new argumentnullexception(nameof(services)); } services.addlogging(); services.addoptions(); // // core abstractions // services.tryaddtransient<httpmessagehandlerbuilder, defaulthttpmessagehandlerbuilder>(); services.tryaddsingleton<ihttpclientfactory, defaulthttpclientfactory>(); // // typed clients // services.tryadd(servicedescriptor.singleton(typeof(itypedhttpclientfactory<>), typeof(defaulttypedhttpclientfactory<>))); // // misc infrastructure // services.tryaddenumerable(servicedescriptor.singleton<ihttpmessagehandlerbuilderfilter, logginghttpmessagehandlerbuilderfilter>()); return services; }
它的内部为ihttpclientfactory接口绑定了defaulthttpclientfactory类。
再看ihttpclientfactory接口中关键的createclient方法:
public httpclient createclient(string name) { if (name == null) { throw new argumentnullexception(nameof(name)); } var entry = _activehandlers.getoradd(name, _entryfactory).value; var client = new httpclient(entry.handler, disposehandler: false); starthandlerentrytimer(entry); var options = _optionsmonitor.get(name); for (var i = 0; i < options.httpclientactions.count; i++) { options.httpclientactions[i](client); } return client; }
httpclient的创建不再是简单的new httpclient(),而是传入了两个参数:httpmessagehandler handler与bool disposehandler。disposehandler参数为false值时表示要重用内部的handler对象。handler参数则从上一句的代码可以看出是以name为键值从一字典中取出,又因为defaulthttpclientfactory类是通过tryaddsingleton方法注册的,也就意味着其为单例,那么这个内部字典便是唯一的,每个键值对应的activehandlertrackingentry对象也是唯一,该对象内部中包含着handler。
下一句代码starthandlerentrytimer(entry); 开启了activehandlertrackingentry对象的过期计时处理。默认过期时间是2分钟。
internal void expirytimer_tick(object state) { var active = (activehandlertrackingentry)state; // the timer callback should be the only one removing from the active collection. if we can't find // our entry in the collection, then this is a bug. var removed = _activehandlers.tryremove(active.name, out var found); debug.assert(removed, "entry not found. we should always be able to remove the entry"); debug.assert(object.referenceequals(active, found.value), "different entry found. the entry should not have been replaced"); // at this point the handler is no longer 'active' and will not be handed out to any new clients. // however we haven't dropped our strong reference to the handler, so we can't yet determine if // there are still any other outstanding references (we know there is at least one). // // we use a different state object to track expired handlers. this allows any other thread that acquired // the 'active' entry to use it without safety problems. var expired = new expiredhandlertrackingentry(active); _expiredhandlers.enqueue(expired); log.handlerexpired(_logger, active.name, active.lifetime); startcleanuptimer(); }
先是将activehandlertrackingentry对象传入新的expiredhandlertrackingentry对象。
public expiredhandlertrackingentry(activehandlertrackingentry other) { name = other.name; _livenesstracker = new weakreference(other.handler); innerhandler = other.handler.innerhandler; }
在其构造方法内部,handler对象通过弱引用方式关联着,不会影响其被gc释放。
然后新建的expiredhandlertrackingentry对象被放入专用的队列。
最后开始清理工作,定时器的时间间隔设定为每10秒一次。
internal void cleanuptimer_tick(object state) { // stop any pending timers, we'll restart the timer if there's anything left to process after cleanup. // // with the scheme we're using it's possible we could end up with some redundant cleanup operations. // this is expected and fine. // // an alternative would be to take a lock during the whole cleanup process. this isn't ideal because it // would result in threads executing expirytimer_tick as they would need to block on cleanup to figure out // whether we need to start the timer. stopcleanuptimer(); try { if (!monitor.tryenter(_cleanupactivelock)) { // we don't want to run a concurrent cleanup cycle. this can happen if the cleanup cycle takes // a long time for some reason. since we're running user code inside dispose, it's definitely // possible. // // if we end up in that position, just make sure the timer gets started again. it should be cheap // to run a 'no-op' cleanup. startcleanuptimer(); return; } var initialcount = _expiredhandlers.count; log.cleanupcyclestart(_logger, initialcount); var stopwatch = valuestopwatch.startnew(); var disposedcount = 0; for (var i = 0; i < initialcount; i++) { // since we're the only one removing from _expired, trydequeue must always succeed. _expiredhandlers.trydequeue(out var entry); debug.assert(entry != null, "entry was null, we should always get an entry back from trydequeue"); if (entry.candispose) { try { entry.innerhandler.dispose(); disposedcount++; } catch (exception ex) { log.cleanupitemfailed(_logger, entry.name, ex); } } else { // if the entry is still live, put it back in the queue so we can process it // during the next cleanup cycle. _expiredhandlers.enqueue(entry); } } log.cleanupcycleend(_logger, stopwatch.getelapsedtime(), disposedcount, _expiredhandlers.count); } finally { monitor.exit(_cleanupactivelock); } // we didn't totally empty the cleanup queue, try again later. if (_expiredhandlers.count > 0) { startcleanuptimer(); } }
上述方法核心是判断是否handler对象已经被gc,如果是的话,则释放其内部资源,即网络连接。
回到最初创建httpclient的代码,会发现并没有传入任何name参数值。这是得益于httpclientfactoryextensions类的扩展方法。
public static httpclient createclient(this ihttpclientfactory factory) { if (factory == null) { throw new argumentnullexception(nameof(factory)); } return factory.createclient(options.defaultname); }
options.defaultname的值为string.empty。
defaulthttpclientfactory缺少无参数的构造方法,唯一的构造方法需要传入多个参数,这也意味着构建它时需要依赖其它一些类,所以目前只适用于在asp.net程序中使用,还无法应用到诸如控制台一类的程序,希望之后官方能够对其继续增强,使得应用范围变得更广。
public defaulthttpclientfactory( iserviceprovider services, iloggerfactory loggerfactory, ioptionsmonitor<httpclientfactoryoptions> optionsmonitor, ienumerable<ihttpmessagehandlerbuilderfilter> filters)
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
上一篇: 李彦宏:人工智能是堪比工业革命的科技浪潮
下一篇: JS实现判断有效的数独算法示例