一. 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,所以主線程走完也沒問題)
{ var optionsBuilder = new DbContextOptionsBuilder<ESHOPContext>(); //傳入連接字符串
optionsBuilder.UseSqlServer(_Configuration.GetConnectionString("EFStr")); //創建EF上下文 ESHOPContext context = new ESHOPContext(optionsBuilder.Options); IBaseService _baseService = new BaseService(context); }
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 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。