第三十二节:比较Core中的内置注入EFCore的注入AutoFac改造后的注入的生命周期问题
Posted lonelyxmas
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第三十二节:比较Core中的内置注入EFCore的注入AutoFac改造后的注入的生命周期问题相关的知识,希望对你有一定的参考价值。
原文:第三十二节:比较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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
以上是关于第三十二节:比较Core中的内置注入EFCore的注入AutoFac改造后的注入的生命周期问题的主要内容,如果未能解决你的问题,请参考以下文章