• 第三十二节:比较Core中的内置注入、EFCore的注入、AutoFac改造后的注入的生命周期问题


    一. Core的内置注入

    类和接口的准备 

     public interface IU1
        {
            string guid { get; set; }
        }
        public interface IU2
        {
            string guid { get; set; }
        }
        public interface IU3
        {
            string guid { get; set; }
        }
        public interface IU4
        {
            string guid { get; set; }
        }
    接口
      public class U1 : IU1
        {
            public string guid { get; set; }
    
            public U1()
            {
                guid = System.Guid.NewGuid().ToString("N");
            }
    
        }
        public class U2 : IU2
        {
            public string guid { get; set; }
    
            public U2()
            {
                guid = System.Guid.NewGuid().ToString("N");
            }
        }
        public class U3 : IU3
        {
            public string guid { get; set; }
    
            public U3()
            {
                guid = System.Guid.NewGuid().ToString("N");
            }
    
        }
        public class U4 : IU4
        {
            public string guid { get; set; }
    
            public U4()
            {
                guid = System.Guid.NewGuid().ToString("N");
            }
        }

    1.测试案例

    (1).比较单次请求、 两次请求 对应的值。

    (2).比较 一次请求 主线程子线程中的值。

    (3).在子线程中加等待时间,看子线程中的对象是否被销毁了,能否继续使用。

    2. 测试步骤

      将U1-U4在ConfigureService按照下面代码进行注册,然后每个类在控制器中注入两次,通过看构造函数中的guid的值是否一样,来判断是否重新创建了。

    ConfigureService中代码

     //内置注入
     services.AddTransient<IU1, U1>();   //瞬时的
     services.AddScoped<IU2, U2>();      //请求内单例
     services.AddSingleton<IU3, U3>();   //全局单例
     services.AddSingleton<IU4>(new U4()); //全局单例

    控制器中的注入代码

        public class HomeController : Controller
        {
            public IU1 U1 { get; }
            public IU1 U11 { get; }
            public IU2 U2 { get; }
            public IU2 U22 { get; }
            public IU3 U3 { get; }
            public IU3 U33 { get; }
            public IU4 U4 { get; }
            public IU4 U44 { get; }
            public HomeController(IU1 u1, IU1 u11, IU2 u2, IU2 u22, IU3 u3, IU3 u33, IU4 u4, IU4 u44, ypfContext context1, ypfContext context2)
            {
                U1 = u1;
                U11 = u11;
                U2 = u2;
                U22 = u22;
                U3 = u3;
                U33 = u33;
                U4 = u4;
                U44 = u44;
            }
    }

    测试代码

     public void myWrite(string msg)
     {
        StreamWriter sw = System.IO.File.AppendText("Log/test.txt");
        //追加文本               
        sw.WriteLineAsync($"{DateTime.Now}:【{msg}】");//自动换行
        sw.Close();
     }
     {
                    myWrite($"内置依赖注入,主线程测试");
                    myWrite($"U1:{U1.guid}");
                    myWrite($"U11:{U11.guid}");
                    myWrite($"U2:{U2.guid}");
                    myWrite($"U22:{U22.guid}");
                    myWrite($"U3:{U3.guid}");
                    myWrite($"U33:{U33.guid}");
                    myWrite($"U4:{U4.guid}");
                    myWrite($"U44:{U44.guid}");
    
                    Task.Run(() =>
                    {
                        Thread.Sleep(5000);
                        myWrite($"下面是Task中的日志:");
                        myWrite($"U1:{U1.guid}");
                        myWrite($"U11:{U11.guid}");
                        myWrite($"U2:{U2.guid}");
                        myWrite($"U22:{U22.guid}");
                        myWrite($"U3:{U3.guid}");
                        myWrite($"U33:{U33.guid}");
                        myWrite($"U4:{U4.guid}");
                        myWrite($"U44:{U44.guid}");
                    });
                    myWrite($"主线程结束");
    }

    测试结果: 两次请求先后进来,记录日志。

     3. 结论

    (1). 瞬时:单次请求内,同一个对象比如U1即使被注入多次(eg:u1,u11),每个注入的实例都是不一样,多次请求就更不一样了。

        请求内单例:单次请求内,同一个对象比如U1被注入多次,每个注入的实例都是一样的;但多次请求是不一样的。

        单例:不管是单次请求还是多次请求,同一个对象比如U1不管被注入多少次,所有的实例都是一样的。

    (2). 对于主线程和子线程这种情况, 注入的同一个实例U1,不管他是瞬时的还是请求内单例的,在主线程和子线程中都是一个对象哦,即U1.guid的在主线程和子线程是相同的.(包括子线程休眠一段时间等着主线程走完,子线程依旧可以获取guid)。

    疑问?

      子线程中为什么还能拿到对象?

       难道是对象没有dispose的原因还是什么? (详见EFCore上下文有dispose,看下面的测试)

    二. EFCore的注入

     1. 测试

      将EFCore的上下文注册为瞬时的,然后在控制器中注入两个,子线程等待一段时间,主线程savechange或者dispose销毁,测试代码如下:

    ConfigureService中代码

    services.AddDbContext<ypfContext>(option => option.UseSqlServer(Configuration.GetConnectionString("EFStr")), ServiceLifetime.Transient);

    上下文代码

     public partial class ypfContext : DbContext
     {
            public string guid { get; set; }
            public ypfContext(DbContextOptions<ypfContext> options)
                : base(options)
            {
                guid = System.Guid.NewGuid().ToString("N");
            }
     }

    控制器中的注入代码

            public ypfContext _dbContext1 { get; }
            public ypfContext _dbContext2 { get; }
    
            public HomeController(ypfContext context1, ypfContext context2)
            {
                _dbContext1 = context1;
                _dbContext2 = context2;
            }

    测试代码

                {
                    //var data = _dbContext.Set<T_SysLoginLog>().ToList();
    
                    myWrite($"EFCore的注入");
                    myWrite($"主线程_dbContext1:{_dbContext1.guid}");
                    myWrite($"主线程_dbContext2:{_dbContext2.guid}");
                    Task.Run(() =>
                    {
                        try
                        {
                            Thread.Sleep(5000);
                            myWrite($"下面是Task中的日志:");
                            myWrite($"子线程_dbContext1:{_dbContext1.guid}");
                            myWrite($"子线程_dbContext2:{_dbContext2.guid}");
                            var data = _dbContext1.Set<T_SysLoginLog>().ToList();
                        }
                        catch (Exception ex)
                        {
                            myWrite($"子线程报错了:{ex.Message}");
                        }
                    });
                    //_dbContext1.SaveChanges();
                    //_dbContext2.SaveChanges();
                    _dbContext1.Dispose();
                    _dbContext2.Dispose();
                    myWrite($"主线程结束");
                }

    测试结果:

     2. 结果与结论

      在ServiceLifetime.Transient瞬时情况下,即使在主线程中savechange、或者dispose、或者什么不做,子线程等足够时间,然主线程已经走完,子线程中依旧可以获取_dbContext1.guid,且和主线程中的_dbContext1.guid是一致的; 主线程中的_dbContext1.guid 和 _dbContext2.guid是不一样的,说明是瞬时的;但是子线程中不能调用EF上下文中的任何方法,会抛异常(Cannot access a disposed object. A common cause of this error is disposing a context that was。。。。。

    (请求内单例和全局单例不需要测试了,默认是请求内单例的)

    推断与解决

    EFCore上下文并不是整个对象都销毁了,因为guid属性还是能拿到的。

    那么如何解决这个问题呢?

    可以在task中new一个新EFCore上下文,不用框架注入的,这样是不受注入影响的。(在实际案例中,可以在Service层写一个方法,然后里面new EfCore上下,把Service对应的接口IxxService类注入到控制器中,这样子线程中的EFCore上下文不受框架注入的影响,而xxService并没有dispose,所以主线程走完也没问题)

    3. 解决上面内置注入留下的疑问

      结合上面内置注入遗留的疑问,可以初步的出来一个结论,如果对象没有实现dispose,采用注入的方式,主线程走完,是不销毁的,子线程仍然可以用。

    (此处还需要进一步推断!!!补充一下手写dispose,destroy方法。)

    三. AutoFac的注入

    1.说明

    (1).文档:https://autofaccn.readthedocs.io/zh/latest/ 中文    https://autofac.org/ 英文

    (2).强调:本节重点演示的Autofac声明周期,关于core 3.x的用法,仅简单介绍

    2. Asp.Net Core3.x中的写法

    (1).通过nuget安装程序集:【AutoFac 5.1.2】【Autofac.Extensions.DependencyInjection 6.0.0】

    (2).在Program类中的CreateHostBuilder方法中添加 .UseServiceProviderFactory(new AutofacServiceProviderFactory())。

        public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .UseServiceProviderFactory(new AutofacServiceProviderFactory())   //AutoFac针对 Core3.x的写法
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    });

    (3).在startup类中新增ConfigureContainer方法,用于注册服务,该方法在ConfigureService执行完后执行。

    PS:这是core3.x中最大的改进出,原ConfigureService不需要做任何变化了。

            /// <summary>
            /// AutoFac的注入
            /// 在这个方法中注册业务,他在ConfigureService后执行
            /// </summary>
            /// <param name="builder"></param>
            public void ConfigureContainer(ContainerBuilder builder)
            {
                builder.RegisterModule<DefaultModule>();
            }

    (4).将ConfigureContainer注册的服务抽离出来一个单独的类,如DefaultModule继承Module类,重写Load方法。

        public class DefaultModule : Module
        {
            protected override void Load(ContainerBuilder builder)
            {       
                //InstancePerLifetimeScope  (每个生命周期作用域)
                //InstancePerMatchingLifetimeScope (每个匹配生命周期作用域)
                //InstancePerOwned (每次被拥有的一个实例)
    
                builder.RegisterType<U1>().As<IU1>().InstancePerDependency(); //瞬时的
                builder.RegisterType<U2>().As<IU2>().InstancePerLifetimeScope();  //每个生命周期作用域单例
    
                builder.RegisterType<U3>().As<IU3>().SingleInstance();    //全局单例
                builder.RegisterType<U4>().As<IU4>().SingleInstance();  //全局单例
            }
        }

    (5). 按照上述注册的代码,然后注入到控制器中,进行测试,得到的结果和Core中的内置注入的结果完全相同。  (这里不再详细粘贴代码了)

                {
                    myWrite($"AutoFac测试,主线程测试");
                    myWrite($"U1:{U1.guid}");
                    myWrite($"U11:{U11.guid}");
                    myWrite($"U2:{U2.guid}");
                    myWrite($"U22:{U22.guid}");
                    myWrite($"U3:{U3.guid}");
                    myWrite($"U33:{U33.guid}");
                    myWrite($"U4:{U4.guid}");
                    myWrite($"U44:{U44.guid}");
    
                    Task.Run(() =>
                    {
                        Thread.Sleep(6000);
                        myWrite($"下面是Task中的日志:");
                        myWrite($"U1:{U1.guid}");
                        myWrite($"U11:{U11.guid}");
                        myWrite($"U2:{U2.guid}");
                        myWrite($"U22:{U22.guid}");
                        myWrite($"U3:{U3.guid}");
                        myWrite($"U33:{U33.guid}");
                        myWrite($"U4:{U4.guid}");
                        myWrite($"U44:{U44.guid}");
                    });
    
                    myWrite($"主线程结束");
                }

    3.生命周期

     (1).InstancePerDependency:瞬时的 (默认就是瞬时的)

     (2).SingleInstance:全局单例

     (3).InstancePerLifetimeScope: 每个生命周期作用域内单例 (在Asp.Net Core中,AutoFac用它来替代了请求内单例,原因详见底部)

     (4).InstancePerMatchingLifetimeScope: 名称匹配下的嵌套作用域内单例。 

     (5).InstancePerRequest:请求内单例。已被弃用,报异常。 (实质:每个请求一个实例建立于每个匹配生命周期一个实例之上)

      PS:使用InstancePerLifetimeScope(每个生命周期作用域一个实例)而不是InstancePerRequest(每个请求一个实例). 以前的ASP.NET集成你可以注册依赖为 InstancePerRequest ,/能保证每次HTTP请求只有唯一的依赖实例被创建. 这是因为Autofac负责 建立每个请求生命周期作用域. 随着 Microsoft.Extensions.DependencyInjection 的引入, 每个请求和其他子生命周期作用域的创建现在是框架提供的 conforming container 的一部分, 因此所有的子生命周期作用域是被同等对待的 - 现在已经不再有特别的 "请求级别作用" .现在不再注册你的依赖为 InstancePerRequest, 而使用 InstancePerLifetimeScope , 你也可以得到相同的行为. 注意如果你在web请求中创建 你自己的生命周期作用域 , 你将会在这些子作用域中得到新的实例.

    补充InstancePerLifetimeScope:

    
    
    var builder = new ContainerBuilder();
    builder.RegisterType<Worker>().InstancePerLifetimeScope();
    using(var scope1 = container.BeginLifetimeScope())
    {
      for(var i = 0; i < 100; i++)
      {// 在这for循环里,每次resolve得到的对象都是同一个实例。
        var w1 = scope1.Resolve<Worker>();
      }
    }
    
    using(var scope2 = container.BeginLifetimeScope())
    {
      for(var i = 0; i < 100; i++)
      {
        // 在这个for循环里,每次得到的对象w2都是同一个实例,但是w2和上面的w1是不同的实例!!!
        var w2 = scope2.Resolve<Worker>();
      }
    }

    补充InstancePerMatchingLifetimeScope:

    var builder = new ContainerBuilder();
    builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("myrequest");
    // Create the lifetime scope using the tag.
    using(var scope1 = container.BeginLifetimeScope("myrequest"))
    {
      for(var i = 0; i < 100; i++)
      {
        var w1 = scope1.Resolve<Worker>();
        using(var scope2 = scope1.BeginLifetimeScope())
        {
          var w2 = scope2.Resolve<Worker>();
    
          //w1和w2在这个循环里永远是一个实例,是相同。这是因为他们在一个命名匹配的生命周期作用域里
        }
      }
    }
    
    // Create another lifetime scope using the tag.
    using(var scope3 = container.BeginLifetimeScope("myrequest"))
    {
      for(var i = 0; i < 100; i++)
      {
        // w3 will be DIFFERENT than the worker resolved in the
        // earlier tagged lifetime scope.
        var w3 = scope3.Resolve<Worker>();
        using(var scope4 = scope3.BeginLifetimeScope())
        {
          var w4 = scope4.Resolve<Worker>();
    
          // w3和w4是同一个实例,但它和上面的w1和w2是不同实例。!!!
        }
      }
    }

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    后端写前端用什么框架
    什么是互联网
    数据的意义
    LR特征维数特别大实时计算问题
    一次线上服务线程数飙到8000
    jsoup 解析html
    做研究的方式
    推荐系统架构文章
    如何识别广告评论
    运营和做事方式
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/12664400.html
Copyright © 2020-2023  润新知