当前位置: 移动技术网 > IT编程>开发语言>.net > DotNetCore深入了解之HttpClientFactory类详解

DotNetCore深入了解之HttpClientFactory类详解

2019年03月20日  | 移动技术网IT编程  | 我要评论

亚洲专区,曾伟蒋梅,考研英语在线培训

当需要向某特定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)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网