zoukankan      html  css  js  c++  java
  • ASP.NET Core 源码阅读笔记(2) ---Microsoft.Extensions.DependencyInjection生命周期管理

        在上一篇文章中我们主要分析了ASP.NET Core默认依赖注入容器的存储和解析,这一篇文章主要补充一下上一篇文章忽略的一些细节:有关服务回收的问题,即服务的生命周期问题。有关源码可以去GitHub上找到。

        这次的主角就是ServiceProvider一人,所有有关生命周期的源码几乎都集中在ServiceProvider.cs这个文件中。

        我们知道服务的生命周期由三种,分别是:

    1. Transient
    2. Scoped
    3. Singleton

        首先给出我的结论:这三种生命周期类别本质上没有区别,服务的生命周期都是由提供服务的容器,即ServiceProvider的生命周期决定的,一个ServiceProvider被回收之后,所有由它产生的Service也随之被回收。由此看来,一个ServiceProvider起了一个ServiceScoped的作用,其实就是这样,ServiceScope本质上就是一个ServiceProvider。

     1     internal class ServiceScope : IServiceScope
     2     {
     3         //仅有一个只读的ServiceProvider字段
     4         private readonly ServiceProvider _scopedProvider;
     5 
     6         public ServiceScope(ServiceProvider scopedProvider)
     7         {
     8             _scopedProvider = scopedProvider;
     9         }
    10 
    11         public IServiceProvider ServiceProvider
    12         {
    13             get { return _scopedProvider; }
    14         }
    15 
    16         public void Dispose()
    17         {
    18             _scopedProvider.Dispose();
    19         }
    20     }

        所以其实也没ServiceScope什么事情,每一个范围都是由ServiceProvider控制的。这么一来,Singleton服务和Scoped服务就是一样的,因为每一个程序都有一个最初的ServiceProvider,我们可以叫它root,或者叫它爸爸,其他的所有ServiceProvider都是由root(爸爸)创建的,自然爸爸的范围最大,所以被爸爸创建的Scoped服务就是所谓的Singleton,因为没有比root(爸爸)范围更大的ServiceProvider了。假如root都被回收了,那么整个程序就该结束了。

     1         public ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors)
     2         {
     3             _root = this;
     4             _table = new ServiceTable(serviceDescriptors);
     5 
     6             _table.Add(typeof(IServiceProvider), new ServiceProviderService());
     7             _table.Add(typeof(IServiceScopeFactory), new ServiceScopeService());
     8             _table.Add(typeof(IEnumerable<>), new OpenIEnumerableService(_table));
     9         }
    10 
    11         // This constructor is called exclusively to create a child scope from the parent
    12         internal ServiceProvider(ServiceProvider parent)
    13         {
    14             //注意下面这句代码
    15             _root = parent._root;
    16             _table = parent._table;
    17         }

        上面贴出来的是ServiceProvider的两个构造函数,注意第二个构造函数:_root字段引用的是爸爸的根,而不是爸爸。假如ServiceProviderA(SPA)创建了SPB,而SPB创建了SPC,那么SPC的_root字段引用的也是SPA。也就是说,所有ServiceProvider之间不是层状结构,不是我们熟悉的树结构,而是一种星型结构,应用程序的第一个ServiceProvider在最中间,其他所有的ServiceProvider的_root字段都是引用了第一个ServiceProvider,除了第一个ServiceProvider,其他的ServiceProvider都是平等的。假如SPC要创建一个Singleton类型的服务,那么直接让_root(也就是SPA)创建即可。

        既然Singleton就是Scoped,那我们就把重点放在Scoped和Transient上。下面是ServiceProvider中有关Scoped和Transient的源码。

     1     internal class ServiceProvider : IServiceProvider, IDisposable
     2     {
     3         private readonly ServiceProvider _root;
     4         private readonly ServiceTable _table;
     5         private bool _disposeCalled;
     6 
     7         //Scoped模式的服务的映射,用于释放服务实例
     8         private readonly Dictionary<IService, object> _resolvedServices = new Dictionary<IService, object>();
     9         //一次性服务的Dispose列表
    10         private List<IDisposable> _transientDisposables;
    11 
    12         internal IServiceCallSite GetResolveCallSite(IService service, ISet<Type> callSiteChain)
    13         {
    14             IServiceCallSite serviceCallSite = service.CreateCallSite(this, callSiteChain);
    15             if (service.Lifetime == ServiceLifetime.Transient)
    16             {
    17                 return new TransientCallSite(serviceCallSite);
    18             }
    19             else if (service.Lifetime == ServiceLifetime.Scoped)
    20             {
    21                 return new ScopedCallSite(service, serviceCallSite);
    22             }
    23             else
    24             {
    25                 return new SingletonCallSite(service, serviceCallSite);
    26             }
    27         }
    28 
    29         private class TransientCallSite : IServiceCallSite
    30         {
    31             private readonly IServiceCallSite _service;
    32             //Involve方法是关键
    33             public object Invoke(ServiceProvider provider)
    34             {
    35                 //触发并放入ServiceProvider的一次性服务释放列表
    36                 return provider.CaptureDisposable(_service.Invoke(provider));
    37             }
    38             //省略Build方法
    39         }
    40         private class ScopedCallSite : IServiceCallSite
    41         {
    42             private readonly IService _key;
    43             private readonly IServiceCallSite _serviceCallSite;
    44             //Invoke方法是关键,省略了其他无关的方法
    45             public virtual object Invoke(ServiceProvider provider)
    46             {
    47                 object resolved;
    48                 //放入ServiceProvider的Scoped服务解析列表
    49                 lock (provider._resolvedServices)
    50                 {
    51                     //如果ResolvedService列表中已经缓存了,就不用再创建
    52                     if (!provider._resolvedServices.TryGetValue(_key, out resolved))
    53                     {
    54                         resolved = _serviceCallSite.Invoke(provider);
    55                         provider._resolvedServices.Add(_key, resolved);
    56                     }
    57                 }
    58                 return resolved;
    59             }
    60         }

        从ServiceProvider的GetResolvedCallSite方法可以看出,当我们要解析一项服务时,先根据服务的生存周期生成不同的CallSite,不同CallSite的Invoke方法决定了ServiceProvider怎么管理这些服务。

        首先看TransientCallSite.Invoke()。里面调用了ServiceProvider的私有方法:CaptureDisposable(),这个方法是捕捉实现了IDisposable接口的服务,如果实现了接口,就将其放入ServiceProvider的_transientDisposables字段中。这个字段顾名思义,是为了释放释放Transient类型的服务而存在的。那如果某个服务没有实现IDisposable接口,那么当服务结束之后ServiceProvider不会保持对它的引用,由于没有变量对它有引用,自然会被GC回收。

        再看ScopedCallSite.Invoke()。首先是在ServiceProvider的_resolvedServices字段中查找相应的服务,如果能找到,说明之前创建过,就无须再创建了。如果还没创建,就将其放入_resolvedServices字段缓存,以备不时之需。貌似Scoped类型服务没有像Transient服务那样有专门的字段管理Dispose,因为这不需要,_resolvedServices字段既可以作为缓存使用,又可以供Dispose使用。

        看一下ServiceProvider的Dispose方法:

            public void Dispose()
            {
                lock (SyncObject)
                {
                    if (_disposeCalled)
                    {
                        return;
                    }
    
                    _disposeCalled = true;
    
                    if (_transientDisposables != null)
                    {
                        foreach (var disposable in _transientDisposables)
                        {
                            disposable.Dispose();
                        }
    
                        _transientDisposables.Clear();
                    }
                    foreach (var entry in _resolvedServices)
                    {
                        (entry.Value as IDisposable)?.Dispose();
                    }
    
                    _resolvedServices.Clear();
                }
            }

        从上面的方法中可以看出,ServiceProvider对待_resolvedServices和_transientDisposables是一样的,并不会特意将Transient的服务频繁释放几次。Transient服务和Scoped服务唯一的区别就在于Transient服务在实例化之前不会去缓存字段中查找是否已经有缓存了,如果有需要,ServiceProvider就会帮你实例化一个。所有ServiceProvider创建的服务(无论是Transient还是Scoped)都只会在ServiceProvider释放的时候才会释放。这会带来一个问题:如果一个ServiceProvider长时间不Dispose,那么如果它要解析Transient类型的服务,会占用大量的内存甚至造成内存泄漏,实例越多,对GC也有影响。由此可以想象,ASP.NET Core的第一个ServiceProvider(也就是root)是不会去解析实现了IDisposable接口的Transient服务的(会被root引用)。它可以创建ServiceScope,由它们的ServiceProvider去解析服务。

        为了避免大量的无用的服务留在内存中,我们要释放无用的服务,比如在RenderView的时候,有关Route的服务肯定已经没用了,为此可以创建不同的ServiceScope。使用using语句可以正确的释放无用的服务。

    1 void DoSthAboutRoute()
    2 {
    3     using (IServiceScope serviceScope = serviceProvider.GetService<IServiceScopeFactory>().CreateScope())
    4     {
    5         IService routeService = serviceScope.ServiceProvider.GetService<IRouteService>();
    6         //....
    7     }
    8 }

        using 语句在结束时会自动调用serviceScope.ServiceProvider.Dispose()方法,所以所有由该ServiceProvider创建的服务都会被及时的释放掉,此时变量serviceScope已经超出了它的作用域,它会被GC标记为垃圾对象。

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

        总结一下:

    1. ServiceProvider负责回收服务,其中_transientDisposables字段负责transient服务,_resolvedServices字段记录Scoped服务,用于缓存和释放。
    2. Singleton服务就是Scoped服务,只不过是应用程序的第一个ServiceProvider(root)的Scoped服务。每一个在应用程序中出现的ServiceProvider都会保持对root的引用,要解析Singleton服务时,直接交给root处理。
    3. ServiceProvider的Dispose方法对transientDisposables和_resolvedServices一视同仁,所以除了transient服务不会在实例化之前查询是否有缓存之外,其他的都和Scoped服务没区别。
    4. 服务的回收主要和服务的创建者ServiceProvider的回收相关(依赖于它的Dispose方法),为了避免内存泄漏,可以使用using 语法及时释放服务。
    5. 这一点在正文中没有提到。Dispose()不是Finalize(),就算你显式地调用某个服务的Dispose方法,它的内存也不会释放掉,因为ServiceProvider还保持这对它的引用。CLR runtime规定只有当某个对象不可达时,才会标记为垃圾对象,才会被GC回收。
  • 相关阅读:
    px, pt, rpx, rem,
    tomcat报错LifecycleException的解决方案
    servlet系列
    tomcat startup.bat双击闪退解决方案。
    代码页
    editplus工具配置
    正则表达式常见字符集
    单片机TM4C123学习(六):看门狗
    stata学习笔记(七):回归分析和稳健性检验
    单片机TM4C123学习(五):UART的使用
  • 原文地址:https://www.cnblogs.com/bill-shooting/p/5550198.html
Copyright © 2011-2022 走看看