跟我一起学.NetCore之依赖注入作用域和对象释放

前言

    上一小节简单阐述了依赖注入及Asp.NetCore中自带依赖注入组件的常规用法,其中提到容器管控了自己创建对象的生命周期,包含了三种生命周期:Singleton、Scoped、Transient, 对于Singleton、Transient相对于Scoped来说比较好理解,其实这里面有一个作用域的概念,也可以理解为根容器和子容器的范围;上一小节中有一个例子中说到,当注入的生命周期为Scoped的时,在同一个请求内,注入的对象都是同一个,这里Asp.NetCore将每个请求作为了一个作用域,在此作用域内,生命周期为Scoped的对象就是同一个;下面简单说说作用域和对象释放的常规知识点;

正文

    作用域这里可以理解为服务范围,由IServiceScope承载,如其源代码所示,每一个服务范围内都一个根容器,如下所示:

// 摘要:
    //     /// The System.IDisposable.Dispose method ends the scope lifetime. Once Dispose
    //     /// is called, any scoped services that have been resolved from /// Microsoft.Extensions.DependencyInjection.IServiceScope.ServiceProvider
    //     will be /// disposed. ///
    public interface IServiceScope : Object, IDisposable
    {
        // IServiceProvider 即代表容器
        //
        // 摘要:
        //     /// The System.IServiceProvider used to resolve dependencies from the scope.
        //     ///
        IServiceProvider ServiceProvider
        {
            get;
        }
    }

    每一个服务范围内可以通过自身IServiceProvider创建对应的子作用域,而任何一个子作用域的IServiceProvider都有对根容器的引用,如下图结构:

    每一个IServiceProvider中都会将其创建的对象存入列表中,针对于继承与IDisposable接口的类型对象会单独存放在一个列表中,当作用域IServiceScope对象的Dispose方法被调用时,最终对应IServicePorvider对应的对象列表也会清空,针对于继承与IDisposable类型的对象会调用其Dispose方法;最后导致对应作用域下容器创建的对象成为垃圾对象,被GC给回收;

    在Asp.NetCore中,框架默认将其分有根作用域(与引用程序同生命周期)和请求作用域(每一个请求一个作用域),通常也会称其为根容器和请求容器,名称分别为ApplicationServices和RequestServices,在程序中获取方式如下:

  •     通过IApplicationBuilder对象可以获取ApplicationServices

  •     通过HttpContext.RequestServices获取RequestServices

    作用域简单举例,在请求作用域下再创建子作用域,分别查看对应不同生命周期对象是否一致,新建一个WebApi项目,针对三个生命周期添加了对应文件,结构如下:

    

对应红框内的文件内容如下:

       细心的小伙伴可能会看到,每个实现类里面都继承了IDisposable接口,这主要是后面显示释放用的,这里先不管;编辑好以上内容之后就将其进行注册,如下:

    接下来就是使用了,这里通过这Action中使用,在当前的请求作用域下创建子作用域,对比每一个生命周期对象,如代码所示:

[HttpGet]
public string Get([FromServices]ITestSingleton testSingleton, [FromServices]ITestSingleton testSingleton1,
                  [FromServices]ITestScoped testScoped, [FromServices]ITestScoped testScoped1,
                  [FromServices]ITestTransient testTransient, [FromServices]ITestTransient testTransient1)
{
    //获取请求作用域(请求容器)
    var requestServices = HttpContext.RequestServices;
    //在请求作用域下创建子作用域
    using(IServiceScope scope = requestServices.CreateScope())
    {
        //在子作用域中通过其容器获取注入的不同生命周期对象
        ITestSingleton testSingleton11 = scope.ServiceProvider.GetService<ITestSingleton>();
        ITestScoped testScoped11 = scope.ServiceProvider.GetService<ITestScoped>();
        ITestTransient testTransient11 = scope.ServiceProvider.GetService<ITestTransient>();
​
        ITestSingleton testSingleton12 = scope.ServiceProvider.GetService<ITestSingleton>();
        ITestScoped testScoped12 = scope.ServiceProvider.GetService<ITestScoped>();
        ITestTransient testTransient12 = scope.ServiceProvider.GetService<ITestTransient>();
        Console.WriteLine("================Singleton=============");
        Console.WriteLine($"请求作用域的ITestSingleton对象:{testSingleton.GetHashCode()}");
        Console.WriteLine($"请求作用域的ITestSingleton1对象:{testSingleton1.GetHashCode()}");
        Console.WriteLine($"请求作用域下子作用域的ITestSingleton11对象:{testSingleton11.GetHashCode()}");
        Console.WriteLine($"请求作用域下子作用域的ITestSingleton12对象:{testSingleton12.GetHashCode()}");
        Console.WriteLine("================Scoped=============");
        Console.WriteLine($"请求作用域的ITestScoped对象:{testScoped.GetHashCode()}");
        Console.WriteLine($"请求作用域的ITestScoped1对象:{testScoped1.GetHashCode()}");
        Console.WriteLine($"请求作用域下子作用域的ITestScoped11对象:{testScoped11.GetHashCode()}");
        Console.WriteLine($"请求作用域下子作用域的ITestScoped12对象:{testScoped12.GetHashCode()}");
        Console.WriteLine("================Transient=============");
        Console.WriteLine($"请求作用域的ITestTransient对象:{testTransient.GetHashCode()}");
        Console.WriteLine($"请求作用域的ITestTransient1对象:{testTransient1.GetHashCode()}");
        Console.WriteLine($"请求作用域下子作用域的ITestTransient11对象:{testTransient11.GetHashCode()}");
        Console.WriteLine($"请求作用域下子作用域的ITestTransient12对象:{testTransient12.GetHashCode()}");
    }
​
    return "TestServiceScope";
​
}

    运行,进行请求,看打印结果:

  •     对于Singleton来说始终不变,因为其是跟随根容器生命周期,引用程序退出才释放;

  •     对于Scoped来说只要在自己的作用域内就是单例的;

  •     对于Transient来说始终创建;

    以上一直在说释放,下面利用继承IDisposable接口释放时会调用对应Dispoable方法的原理简单演示各个生命周期的释放时机;在Controller中增加几个Action方法,如下:

    这里使用IHostApplicationLifetime中的StopApplication模拟关闭程序释放单例下的对象;运行看效果:

    使用坑:不要从根容器中获取Transient生命周期的对象,因为通过根容器创建的对象不会回收,除非等到应用程序退出,这样会导致内存泄露;如下演示:

    

新增Action方法:

运行,发送请求看结果:

总结

    作用域及对象释放就简单说这么多,容器只管理自己创建出来的对象生命周期;下一节说说使用第三方组件扩展依赖注入功能;

原文地址:https://www.cnblogs.com/zoe-zyq/p/13392299.html