前言

自从.Net Core引入IOC相关的体系之后,关于它的讨论就从来没有停止过,因为它是.Net Core体系的底层框架,你只要使用了.Net Core的时候就必然会用到它。当然关于使用它的过程中产生的问题也从来没停止过。我对待问题的态度向来都是,如果你踩到了坑,说明你还没有足够了解它,所以我们对它认知的突破,很多时候是遇到了问题并解决了问题。今天的话题呢,也是一个群友在研究.Net Core作用域的过程中产生的疑问,博主呢对这个问题也很有兴趣,就借此机会探究了一下,把自己研究结果分享给大家。

简单演示

在日常的开发中使用CreateScope()CreateAsyncScope()的场景可能没有那么多,但是在ASP.NET Core底层的话这是核心设计,在上篇文章<解决ASP.NET Core在Task中使用IServiceProvider的问题>中提到过,ASP.NET Core会为每次请求创建一个Scope,也就是咱们这次提到的作用域。使用的方法有很简单,本质就是IServiceProvider的一个扩展方法。咱们今天主要说的就是ServiceLifetime.Scoped这个比较特殊的生命周期,在Scope内是如何工作的,原始点的写法其实就是

  1. IServiceCollection services = new ServiceCollection();
  2. services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" });
  3. IServiceProvider serviceProvider = services.BuildServiceProvider();
  4. using (IServiceScope serviceScope = serviceProvider.CreateScope())
  5. {
  6. var personOne = serviceScope.ServiceProvider.GetService<Person>();
  7. Console.WriteLine(person.Name);
  8. }

如果在ASP.NET Core框架里那玩法就多了,只要有IServiceProvide的地方都可以使用CreateScope()CreateAsyncScope()方法,简单演示一下,但是如果感觉自己把握不住的话还是提前自己试验一下

  1. [HttpGet]
  2. public async Task<object> JudgeScope([FromServices]IServiceProvider scopeProvider)
  3. {
  4. using IServiceScope scope = HttpContext.RequestServices.CreateScope();
  5. Person person = scope.ServiceProvider.GetService<Person>();
  6. await using (AsyncServiceScope scope2 = scopeProvider.CreateAsyncScope())
  7. {
  8. Person person2 = scope2.ServiceProvider.GetService<Person>();
  9. }
  10. return person;
  11. }

源码探究

通过上面的示例,我们可以看到其实关于IServiceScope的操作部分就是三个核心。

  • 通过CreateScope()CreateAsyncScope()方法创建服务作用域。
  • 使用GetService相关的方法创建需要的对象实例。
  • 用完了作用域之后通过使用Dispose()或者DisposeAsync()方法(using的方式同理)释放作用域。

先说AsyncServiceScope

为了怕大家心里有疑虑,因为使用CreateScope()方法创建出来的是IServiceScope实例,使用CreateAsyncScope方法创建的是AsyncServiceScope实例。咱们这里先来说一下AsyncServiceScopeIServiceScope的关系,看了之后大家就不用惦记它了,先来看一下CreateAsyncScope()方法的定义[点击查看源码]

  1. public static AsyncServiceScope CreateAsyncScope(this IServiceProvider provider)
  2. {
  3. return new AsyncServiceScope(provider.CreateScope());
  4. }

方法就是返回AsyncServiceScope实例,接下来来看一下这个类的定义[点击查看源码]

  1. public readonly struct AsyncServiceScope : IServiceScope, IAsyncDisposable
  2. {
  3. private readonly IServiceScope _serviceScope;
  4. public AsyncServiceScope(IServiceScope serviceScope)
  5. {
  6. //AsyncServiceScope也是IServiceScope实例构建起来的
  7. _serviceScope = serviceScope ?? throw new ArgumentNullException(nameof(serviceScope));
  8. }
  9. //ServiceProvider也是直接在IServiceScope实例中直接获取的
  10. public IServiceProvider ServiceProvider => _serviceScope.ServiceProvider;
  11. //同步释放
  12. public void Dispose()
  13. {
  14. _serviceScope.Dispose();
  15. }
  16. //异步释放
  17. public ValueTask DisposeAsync()
  18. {
  19. //因为IAsyncDisposable的ServiceProvider能继续创建作用域
  20. //使用CreateScope或CreateAsyncScope方法
  21. if (_serviceScope is IAsyncDisposable ad)
  22. {
  23. return ad.DisposeAsync();
  24. }
  25. _serviceScope.Dispose();
  26. return default;
  27. }
  28. }

通过源码我们可以看到AsyncServiceScope本身是包装了IServiceScope实例,它本身也是实现了IServiceScope接口并且同时IAsyncDisposable接口以便可以异步调用释放。相信大家都知道,实现了IDispose接口可以使用using IServiceScope scope = HttpContext.RequestServices.CreateScope()的方式,它编译完之后其实是

  1. IServiceScope scope = HttpContext.RequestServices.CreateScope();
  2. try
  3. {
  4. //具体操作
  5. }
  6. finally
  7. {
  8. scope.Dispose();
  9. }

实现了IAsyncDisposable接口可以使用await using (AsyncServiceScope scope2 = scopeProvider.CreateAsyncScope())的方式,它编译完的代码则是

  1. AsyncServiceScope scope2 = scopeProvider.CreateAsyncScope();
  2. try
  3. {
  4. //具体操作
  5. }
  6. finally
  7. {
  8. await scope2.DisposeAsync();
  9. }

打消了这个疑虑,相信大家对它们的关系有了了解,本质就是包装了一下IServiceScope实例。

由创建开始

接下来我们可以专心的看一下IServiceScope相关的实现,IServiceScope的创建则是来自IServiceProvider的扩展方法CreateScope(),首先看下它的定义[点击查看源码]

  1. public static IServiceScope CreateScope(this IServiceProvider provider)
  2. {
  3. return provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
  4. }

好吧,短短的一行代码,我们可以得到两个比较重要的信息

  • 首先获取到的IServiceScopeFactory实例,看过上篇文章的可以知道,默认情况通过IServiceScopeFactory实例获取的是根容器的实例。
  • 其次IServiceProvider的CreateScope扩展方法,本质是调用的IServiceScopeFactoryCreateScope方法。

接下来我们就看看下IServiceScopeFactory默认实现类中关于CreateScope()方法的定义,在ServiceProviderEngineScope类中[点击查看源码]

  1. internal ServiceProvider RootProvider { get; }
  2. public IServiceScope CreateScope() => RootProvider.CreateScope();

这里毫无疑问了RootProvider属性里的实例都是来自根容器,而CreateScope()方法则是调用的ServiceProviderCreateScope()方法。看下ServiceProvider类的CreateScope方法定义

[点击查看源码]

  1. private bool _disposed;
  2. internal IServiceScope CreateScope()
  3. {
  4. //判断当前ServiceProvider是否被释放
  5. if (_disposed)
  6. {
  7. //如果已经释放则直接抛出异常
  8. ThrowHelper.ThrowObjectDisposedException();
  9. }
  10. //创建ServiceProviderEngineScope实例
  11. return new ServiceProviderEngineScope(this, isRootScope: false);
  12. }

通过上面的代码我们可以看到CreateScope()方法,本质是创建了一个ServiceProviderEngineScope方法实例。通过创建的这一行代码,好巧不巧又可以得到两个重要的信息。

  • 一是ServiceProviderEngineScope构造函数的第一个参数,传递的是当前的ServiceProvider实例。
  • 二是ServiceProviderEngineScope构造函数的第二个参数叫isRootScope值给的是false,说明当前ServiceProviderEngineScope实例不是根作用域,也就是我们说的子作用域。

大致看一下ServiceProviderEngineScope构造函数的实现[点击查看源码]

  1. internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAsyncDisposable, IServiceScopeFactory
  2. {
  3. internal Dictionary<ServiceCacheKey, object> ResolvedServices { get; }
  4. internal object Sync => ResolvedServices;
  5. internal ServiceProvider RootProvider { get; }
  6. public bool IsRootScope { get; }
  7. //IServiceProvider的ServiceProvider属性则是赋值的当前实例
  8. public IServiceProvider ServiceProvider => this;
  9. public ServiceProviderEngineScope(ServiceProvider provider, bool isRootScope)
  10. {
  11. //用来存储当前作用域管理的对象实例
  12. ResolvedServices = new Dictionary<ServiceCacheKey, object>();
  13. //创建当前实例的根容器
  14. RootProvider = provider;
  15. //标识当前作用域是否是根容器
  16. IsRootScope = isRootScope;
  17. }
  18. }

下大致看一下,因为接下来会涉及到ServiceProviderEngineScope这个类。到目前为止涉及到了两个比较重要的类ServiceProviderServiceProviderEngineScope,它们都是实现了IServiceProvider接口。看起来有点乱的样子,不过我们可以姑且这么理解。ServiceProvider是IServiceProvider的直系实现类,作为IServiceProvider根容器的实现。ServiceProviderEngineScope是用于,通过IServiceProvider创建作用域时表现出来的实例,也就是非根容器的实例。

探究获取方法

关于上面的介绍,我们其实探究了一点serviceProvider.CreateScope(),接下来我们就需要看一下关于获取的相关操作,也就是GetService方法相关,它的使用形式是serviceScope.ServiceProvider.GetService<T>()。上面我们提到过ServiceProviderEngineScopeServiceProvider属性实例则是当前ServiceProviderEngineScope的实例,所以我们直接去看ServiceProviderEngineScopeGetService方法[点击查看源码]

  1. internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAsyncDisposable, IServiceScopeFactory
  2. {
  3. private bool _disposed;
  4. internal ServiceProvider RootProvider { get; }
  5. public object GetService(Type serviceType)
  6. {
  7. //判断当前实例是否释放
  8. if (_disposed)
  9. {
  10. //如果已经释放则直接抛出异常
  11. ThrowHelper.ThrowObjectDisposedException();
  12. }
  13. return RootProvider.GetService(serviceType, this);
  14. }
  15. }

看着挺乱的,各种跳转各种调用。不过本质只设计到两个类ServiceProviderServiceProviderEngineScope,先说明一下,省的大家看着整蒙圈了。通过最后一句代码,我们又能得到两个比较重要的信息。

  • ServiceProviderEngineScope的GetService方法,本质是在调用RootProvider的GetService方法。通过前面咱们的源码分析可以知道RootProvider属性的值是ServiceProvider实例也就是代表的根容器。
  • 调用RootProvider的GetService方法的时候传递了当前ServiceProviderEngineScope实例。

接下来就可以直接找到ServiceProvider的GetService方法了,看一下里面的具体相关实现[点击查看源码]

  1. public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDisposable
  2. {
  3. private bool _disposed;
  4. private ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>> _realizedServices;
  5. private readonly Func<Type, Func<ServiceProviderEngineScope, object>> _createServiceAccessor;
  6. internal ServiceProviderEngine _engine;
  7. internal ServiceProvider()
  8. {
  9. _createServiceAccessor = CreateServiceAccessor;
  10. _realizedServices = new ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>>();
  11. }
  12. internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
  13. {
  14. //判断当前实例是否释放
  15. if (_disposed)
  16. {
  17. ThrowHelper.ThrowObjectDisposedException();
  18. }
  19. //缓存获取服务实例委托的字典,值为要获取实例的类型,值是创建实例的委托
  20. //_createServiceAccessor本质是CreateServiceAccessor方法委托
  21. Func<ServiceProviderEngineScope, object> realizedService = _realizedServices.GetOrAdd(serviceType, _createServiceAccessor);
  22. OnResolve(serviceType, serviceProviderEngineScope);
  23. DependencyInjectionEventSource.Log.ServiceResolved(this, serviceType);
  24. //执行realizedService委托,传递的是ServiceProviderEngineScope实例
  25. var result = realizedService.Invoke(serviceProviderEngineScope);
  26. System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceType));
  27. return result;
  28. }
  29. private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serviceType)
  30. {
  31. //获取ServiceCallSite,其实就是获取要解析对象的实例相关信息
  32. ServiceCallSite callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
  33. if (callSite != null)
  34. {
  35. DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceType, callSite);
  36. OnCreate(callSite);
  37. //咱们当前讨论的是Scope周期对象的问题,这个方法描述的是生命周期为ServiceLifetime.Singleton的情况,可以跳过这个逻辑
  38. //如果是单例情况,则直接在根容器中直接去操作对象实例,和当前的ServiceProviderEngineScope无关
  39. if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
  40. {
  41. object value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
  42. return scope => value;
  43. }
  44. //解析ServiceCallSite里的信息
  45. return _engine.RealizeService(callSite);
  46. }
  47. return _ => null;
  48. }
  49. }

这里我们看下CallSiteFactory.GetCallSite方法,先来说一下这个方法是做啥的。我们要获取某个类型的实例(可以理解为我们演示示例里的Person类),但是我们注册类相关的信息的时候(比如上面的services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" }))涉及到几种方式,比如AddScoped<T>Add<T>(Func<IServiceProvider,object>),我们需要知道创建类型实例的时候使用哪种方式(比如我们的Person是使用委托的这种方式),ServiceCallSite正是存储的类型和如何创建这个类型的工厂相关的信息。我们来看一下GetCallSite方法的核心操作[点击查看源码]

  1. private readonly ConcurrentDictionary<ServiceCacheKey, ServiceCallSite> _callSiteCache = new ConcurrentDictionary<ServiceCacheKey, ServiceCallSite>();
  2. private ServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot)
  3. {
  4. if (serviceType == descriptor.ServiceType)
  5. {
  6. //要获取的类型会被包装成ServiceCacheKey
  7. ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceType, slot);
  8. //在缓存中获取ServiceCallSite实例,可以理解为设计模式中的享元模式
  9. if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite serviceCallSite))
  10. {
  11. return serviceCallSite;
  12. }
  13. ServiceCallSite callSite;
  14. //根据ServiceDescriptor.Lifetime包装ResultCache
  15. var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
  16. //ServiceDescriptor就是我们添加到IServiceCollection的最终形式
  17. //我们注册服务的时候本质就是在IServiceCollection里添加ServiceDescriptor实例
  18. //AddScope<T>()这种形式
  19. if (descriptor.ImplementationInstance != null)
  20. {
  21. callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.ImplementationInstance);
  22. }
  23. //AddScope(Func<IServiceProvider,object>)形式
  24. else if (descriptor.ImplementationFactory != null)
  25. {
  26. callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory);
  27. }
  28. //AddScope<T,TImpl>()形式
  29. else if (descriptor.ImplementationType != null)
  30. {
  31. callSite = CreateConstructorCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationType, callSiteChain);
  32. }
  33. else
  34. {
  35. throw new InvalidOperationException(SR.InvalidServiceDescriptor);
  36. }
  37. //将创建的ServiceCallSite缓存起来
  38. return _callSiteCache[callSiteKey] = callSite;
  39. }
  40. return null;
  41. }

而解析ServiceCallSite实例的方法RealizeService(ServiceCallSite)则是在ServiceProviderEngine类中,看一下相关实现[点击查看源码]

  1. public override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
  2. {
  3. int callCount = 0;
  4. return scope =>
  5. {
  6. //核心代码是Resolve方法,这里的scope则是ServiceProviderEngineScope
  7. //即我们上面通过CreateScope()创建的实例
  8. var result = CallSiteRuntimeResolver.Instance.Resolve(callSite, scope);
  9. if (Interlocked.Increment(ref callCount) == 2)
  10. {
  11. _ = ThreadPool.UnsafeQueueUserWorkItem(_ =>
  12. {
  13. try
  14. {
  15. _serviceProvider.ReplaceServiceAccessor(callSite, base.RealizeService(callSite));
  16. }
  17. catch (Exception ex)
  18. {
  19. //省略掉非核心代码
  20. }
  21. },
  22. null);
  23. }
  24. return result;
  25. };
  26. }

上面我们看到的RealizeService()方法返回的是一个委托,而调用这个委托的地方则是上面源码中看到的realizedService.Invoke(serviceProviderEngineScope),核心操作在CallSiteRuntimeResolver.Instance.Resolve()方法,Resolve方法的核心逻辑在VisitCallSite()方法,看一下它的实现方式[点击查看源码]

  1. protected virtual TResult VisitCallSite(ServiceCallSite callSite, TArgument argument)
  2. {
  3. if (!_stackGuard.TryEnterOnCurrentStack())
  4. {
  5. return _stackGuard.RunOnEmptyStack((c, a) => VisitCallSite(c, a), callSite, argument);
  6. }
  7. switch (callSite.Cache.Location)
  8. {
  9. //ServiceLifetime.Singleton单例情况
  10. case CallSiteResultCacheLocation.Root:
  11. return VisitRootCache(callSite, argument);
  12. //ServiceLifetime.Scoped作用域情况,也就是咱们关注的情况
  13. case CallSiteResultCacheLocation.Scope:
  14. return VisitScopeCache(callSite, argument);
  15. //ServiceLifetime.Transient瞬时情况
  16. case CallSiteResultCacheLocation.Dispose:
  17. return VisitDisposeCache(callSite, argument);
  18. case CallSiteResultCacheLocation.None:
  19. return VisitNoCache(callSite, argument);
  20. default:
  21. throw new ArgumentOutOfRangeException();
  22. }
  23. }

因为我们关注的是CallSiteResultCacheLocation.Scope这种情况所以我们重点关注的是VisitScopeCache()这段方法逻辑,CallSiteRuntimeResolver的VisitCache()方法[点击查看源码]

  1. protected override object VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
  2. {
  3. //咱们关注的是Scope的情况,所以重点在VisitCache方法
  4. return context.Scope.IsRootScope ?
  5. VisitRootCache(callSite, context) :
  6. VisitCache(callSite, context, context.Scope, RuntimeResolverLock.Scope);
  7. }
  8. //这里的serviceProviderEngine参数就是我们传递进来的ServiceProviderEngineScope实例
  9. private object VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
  10. {
  11. bool lockTaken = false;
  12. //获取ServiceProviderEngineScope的Sync属性
  13. object sync = serviceProviderEngine.Sync;
  14. //获取ServiceProviderEngineScope的ResolvedServices属性
  15. Dictionary<ServiceCacheKey, object> resolvedServices = serviceProviderEngine.ResolvedServices;
  16. //加锁
  17. if ((context.AcquiredLocks & lockType) == 0)
  18. {
  19. Monitor.Enter(sync, ref lockTaken);
  20. }
  21. try
  22. {
  23. //判断ServiceProviderEngineScope的ResolvedServices的属性里是否包含该类型实例
  24. //当前作用域内只有一个实例,所以缓存起来
  25. if (resolvedServices.TryGetValue(callSite.Cache.Key, out object resolved))
  26. {
  27. return resolved;
  28. }
  29. //当前Scope没创建过实例的话则创建
  30. resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
  31. {
  32. Scope = serviceProviderEngine,
  33. AcquiredLocks = context.AcquiredLocks | lockType
  34. });
  35. //判断当前类型实例是否是IDispose想实例
  36. serviceProviderEngine.CaptureDisposable(resolved);
  37. //给ServiceProviderEngineScope的ResolvedServices的属性添加缓存实例
  38. resolvedServices.Add(callSite.Cache.Key, resolved);
  39. return resolved;
  40. }
  41. finally
  42. {
  43. if (lockTaken)
  44. {
  45. Monitor.Exit(sync);
  46. }
  47. }
  48. }
  49. protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
  50. {
  51. //比如我们上面的services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" })
  52. //对应的Kind则是CallSiteKind.Factory
  53. switch (callSite.Kind)
  54. {
  55. case CallSiteKind.Factory:
  56. //调用了VisitFactory方法
  57. return VisitFactory((FactoryCallSite)callSite, argument);
  58. case CallSiteKind.IEnumerable:
  59. return VisitIEnumerable((IEnumerableCallSite)callSite, argument);
  60. case CallSiteKind.Constructor:
  61. return VisitConstructor((ConstructorCallSite)callSite, argument);
  62. case CallSiteKind.Constant:
  63. return VisitConstant((ConstantCallSite)callSite, argument);
  64. case CallSiteKind.ServiceProvider:
  65. return VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
  66. default:
  67. throw new NotSupportedException(SR.Format(SR.CallSiteTypeNotSupported, callSite.GetType()));
  68. }
  69. }
  70. protected override object VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
  71. {
  72. //调用我们注册的services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" })
  73. //FactoryCallSite的Factory即是provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" }
  74. //context.Scope则是我们通过CreateScope创建的实例
  75. //返回的结果就是调用当前委托得到的实例即我们实例中的Person实例
  76. return factoryCallSite.Factory(context.Scope);
  77. }

回过头来说一下咱们上面展示的代码,被调用执行的地方就是GetService方法里的realizedService.Invoke(serviceProviderEngineScope)的这段代码。上面的执行逻辑里涉及到了ServiceProviderEngineScope里的几个操作,比如ResolvedServices属性和CaptureDisposable()方法,看一下相关的代码

  1. internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAsyncDisposable, IServiceScopeFactory
  2. {
  3. internal IList<object> Disposables => _disposables ?? (IList<object>)Array.Empty<object>();
  4. private bool _disposed;
  5. private List<object> _disposables;
  6. internal Dictionary<ServiceCacheKey, object> ResolvedServices { get; }
  7. public ServiceProviderEngineScope(ServiceProvider provider, bool isRootScope)
  8. {
  9. ResolvedServices = new Dictionary<ServiceCacheKey, object>();
  10. }
  11. internal object CaptureDisposable(object service)
  12. {
  13. //判断实例是否实现了IDisposable或IAsyncDisposable接口,因为这种需要在当前作用域是否的时候一起释放
  14. if (ReferenceEquals(this, service) || !(service is IDisposable || service is IAsyncDisposable))
  15. {
  16. return service;
  17. }
  18. bool disposed = false;
  19. lock (Sync)
  20. {
  21. //判断当前作用域是否释放
  22. if (_disposed)
  23. {
  24. disposed = true;
  25. }
  26. else
  27. { //如果满足则添加到_disposables待释放集合,以便作用域释放的时候一起释放
  28. _disposables ??= new List<object>();
  29. _disposables.Add(service);
  30. }
  31. }
  32. //如果当前作用域已经被释放则直接释放当前实例
  33. if (disposed)
  34. {
  35. //前提是服务实例是实现IDisposable或IAsyncDisposable接口的
  36. if (service is IDisposable disposable)
  37. {
  38. disposable.Dispose();
  39. }
  40. else
  41. {
  42. Task.Run(() => ((IAsyncDisposable)service).DisposeAsync().AsTask()).GetAwaiter().GetResult();
  43. }
  44. ThrowHelper.ThrowObjectDisposedException();
  45. }
  46. return service;
  47. }
  48. }

上面关于ServiceProviderEngineScope类中涉及到GetService()方法的相关逻辑已经展示的差不多了,涉及到的比较多,而且看着比较乱。不过如果理解了思路还是比较清晰的,咱们来做一下一个大概的总结。

  • 首先,需要获取ServiceCallSite,在方法GetCallSite()中,其实就是获取要解析对象的实例相关信息。我们需要知道创建类型实例的时候使用哪种方式(比如我们的Person是使用委托的这种方式),其中也包括该对象创建的类型、创建工厂、生命周期类型。
  • 然后,得到ServiceCallSite实例之后,我们就可以通过实例创建的信息去创建信息,在方法RealizeService()里。根据不同类型创建方式和生命周期,判断如何创建对象,即对象存放位置。
  • 最后,如果是单例模式,则在根容器中解析这个对象,位置当然也是存储在根容器中,全局唯一。如果是瞬时模式,则直接返回创建的对象实例,不进行任何存储,但是需要判断实例是否实现了IDisposable或IAsyncDisposable接口,如果是则加入当前ServiceProviderEngineScope实例的_disposables集合。如果是Scope模式就比较特殊了,因为Scope需要在当前ServiceProviderEngineScope中存储保证当前作用域唯一,则需要添加到ResolvedServices属性的字典里,同时也需要判断是否需要添加到_disposables集合里。

这就可以解释ServiceProviderEngineScope针对不同生命周期的存储方式了,单例的情况创建和存储都是在根容器中,瞬时的情况下则每次都创建新的实例且不进行存储,Scope的情况下则是存储在当前的ResolvedServices中享元起来可以在当前作用域内重复使用。

关于结束释放

前面咱们看了下关于作用域创建,在做用户获取对象的相关逻辑。接下来我们来看一下三件套的最后一个步骤,释放逻辑相关的。这个逻辑比较简单,上面咱们或多或少的也说过了一点,释放分为同步释放和异步释放两种情况,咱们看一下同步释放的相关实现[点击查看源码]

  1. internal Dictionary<ServiceCacheKey, object> ResolvedServices { get; }
  2. internal object Sync => ResolvedServices;
  3. private bool _disposed;
  4. private List<object> _disposables;
  5. public void Dispose()
  6. {
  7. List<object> toDispose = BeginDispose();
  8. if (toDispose != null)
  9. {
  10. for (int i = toDispose.Count - 1; i >= 0; i--)
  11. {
  12. //模仿栈模式,最后创建的最先释放
  13. if (toDispose[i] is IDisposable disposable)
  14. {
  15. //释放的正式实现了IDisposable接口的对象
  16. disposable.Dispose();
  17. }
  18. else
  19. {
  20. throw new InvalidOperationException(SR.Format(SR.AsyncDisposableServiceDispose, TypeNameHelper.GetTypeDisplayName(toDispose[i])));
  21. }
  22. }
  23. }
  24. }
  25. private List<object> BeginDispose()
  26. {
  27. //本质就是锁住当前存储对象的集合,不允许进行任何操作
  28. lock (Sync)
  29. {
  30. //如果已经释放过了则直接返回
  31. if (_disposed)
  32. {
  33. return null;
  34. }
  35. DependencyInjectionEventSource.Log.ScopeDisposed(RootProvider.GetHashCode(), ResolvedServices.Count, _disposables?.Count ?? 0);
  36. //先把释放标识设置了
  37. _disposed = true;
  38. }
  39. //判断是否是根容器释放
  40. if (IsRootScope && !RootProvider.IsDisposed())
  41. {
  42. RootProvider.Dispose();
  43. }
  44. return _disposables;
  45. }

其实主要逻辑就是循环释放_disposables里的所有对象,也就是实现了IDisposable接口的对象。接下来咱们再来看一下异步释放的相关逻辑。

  1. public ValueTask DisposeAsync()
  2. {
  3. List<object> toDispose = BeginDispose();
  4. if (toDispose != null)
  5. {
  6. try
  7. {
  8. for (int i = toDispose.Count - 1; i >= 0; i--)
  9. {
  10. object disposable = toDispose[i];
  11. //判断是否是实现了IAsyncDisposable接口的对象
  12. if (disposable is IAsyncDisposable asyncDisposable)
  13. {
  14. //获取DisposeAsync方法返回值也就是ValueTask
  15. ValueTask vt = asyncDisposable.DisposeAsync();
  16. if (!vt.IsCompletedSuccessfully)
  17. {
  18. return Await(i, vt, toDispose);
  19. }
  20. //阻塞等待DisposeAsync执行完成
  21. vt.GetAwaiter().GetResult();
  22. }
  23. else
  24. {
  25. ((IDisposable)disposable).Dispose();
  26. }
  27. }
  28. }
  29. catch (Exception ex)
  30. {
  31. return new ValueTask(Task.FromException(ex));
  32. }
  33. }
  34. return default;
  35. static async ValueTask Await(int i, ValueTask vt, List<object> toDispose)
  36. {
  37. //等待DisposeAsync方法里的逻辑执行完成
  38. await vt.ConfigureAwait(false);
  39. i--;
  40. for (; i >= 0; i--)
  41. {
  42. object disposable = toDispose[i];
  43. if (disposable is IAsyncDisposable asyncDisposable)
  44. {
  45. //等待DisposeAsync执行完成
  46. await asyncDisposable.DisposeAsync().ConfigureAwait(false);
  47. }
  48. else
  49. {
  50. ((IDisposable)disposable).Dispose();
  51. }
  52. }
  53. }
  54. }

其实核心逻辑和同步释放是一致的,只是IAsyncDisposable接口中的DisposeAsync()方法返回的异步相关的ValueTask所以需要进行一些等待相关的操作。不过本质都是循环释放_disposables里的数据,而这些数据正是当前作用域里里实现了IDisposable或IAsyncDisposable接口的实例。

使用CreateScope()GetService()方法的时候,都会判断当前作用域是否释放,而这个标识正是在Dispose()DisposeAsync()置为true的。我们上面文章中的那个异常的引发点也正是这里,也正是因为作用域被释放了表示为置为true了,所以GetService才会直接抛出异常。

群友问题解答

关于ServiceProviderEngineScope重要的相关实现,咱们上面已经大致的讲解过了。其实探索它的原动力就是因为群友遇到的一些关于这方面的疑问,如果了解了它的实现的话便能轻松的解除心中的疑问,还原一下当时有疑问的代码

  1. IServiceCollection services = new ServiceCollection();
  2. services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" });
  3. IServiceProvider serviceProvider = services.BuildServiceProvider();
  4. Person person = null;
  5. using (IServiceScope serviceScope = serviceProvider.CreateScope())
  6. {
  7. person = serviceScope.ServiceProvider.GetService<Person>();
  8. Console.WriteLine(person.Name);
  9. }
  10. if (person == null)
  11. {
  12. Console.WriteLine("Person被回收了");
  13. }

代码大致描述的就是当时的这么一个场景,这里毫无疑问哈,完全判断不出来Person是否已经被回收。通过上面的源码咱们就可以知道,无论是掉用ServiceProviderEngineScope的是Dispose()DisposeAsync()方法(using上面咱们说过了就是语法糖),其实都是调用了当前作用域内实现了IDisposableIAsyncDisposable接口的实例里的Dispose()DisposeAsync()方法。

  • 即使当前实例实现了IDisposableIAsyncDisposable接口,且调用了实例内的Dispose()DisposeAsync()方法,也不意味着当前对象已经被释放了,因为我们用Dispose方法里多半是这个对象里引用的非托管代码的释放工作,并不意味这当前对象被释放了。
  • IServiceScope实现类ServiceProviderEngineScope里ResolvedServices属性享元的实例,也就是生命周期为ServiceLifetime.Scoped的实例。这些实例何时被回收是取决于两点,一是当前实例的访问是否超出当前作用域,二是当前对象是否有被引用。上面的示例中IServiceScope实例虽然已经超出作用了(因为在using括号之外了),但是Person外出的栈里还引用着ResolvedServices字典里Person对象的实例,所以GC即垃圾回收机制并不会回收这个实例,因为它还在被引用。那就意味着它不能被释放,也就不存在Person实例被回收这么一说了。

所以,上面的问题说起来就是IServiceScope主要解决的是对象取的问题,也就是我用我的字典属性保留了需要保留的对象实例,可以释放被声明可以释放的操作(比如非托管资源的释放)。但是作用域本身的回收和内部管理的对象的回收是交给GC来负责的。

细想一下就会明白了,托管对象的回收本身就是垃圾回收处理的,就和你自己写单例模式或者直接new一个对象实例的时候,你也没考虑对象的回收问题,因为垃圾回收机制已经帮你处理了。

总结

在.Net Core体系中IOC一直是核心模块,且关于Scope的作用域的问题,一直会有人产生疑问,想更深刻的了解一下还是得多拿一些时间研究一下。有些知识不是靠一时半会的学就能牢牢地掌握,需要日常不断的积累和不断的解决问题,才能掌握的更多。因为设计到的源码比较多,而且不熟悉的话可能不是很好理解,所以还需要平时的积累,积累的越多能解决的问题越多,才能避免入坑。好了大致总结一下

  • 当我们使用CreateScope()CreateAsyncScope()创建出ServiceProviderEngineScopeAsyncServiceScope实例的时候,即我们通常描述的作用域。这个实例里包含了ResolvedServices属性和Disposables属性,分别保存当前作用域内即生命周期为ServiceLifetime.Scoped实例和实现了IDisposableIAsyncDisposable接口的实例。
  • 使用GetService()方法在当前作用域内获取实例的时候,会根据服务注册时使用的生命周期判断是否加入到当前作用域里享元的实例。其中单例来自于根容器,瞬时的每次都需要创建新的实例所以不需要保存,只有生命周期为ServiceLifetime.Scoped才保存。瞬时的和Scope的对象创建出来的时候都会判断是否实现了IDisposableIAsyncDisposable接口,如果是则加入到Disposables属性的集合里用于释放。
  • 当前作用域被释放的时候,即调用IServiceScope实例Dispose()相关方法的时候,会遍历Disposables集合里的对象进行Dispose相关方法调用,并不是回收托管到当前作用域内的对象,因为对象何时被回收取决于GC即垃圾回收机制。

好了到这里差不多,欢迎大家多多交流。寒冬已至,希望大家都有御寒的方法。分享一下看到过的一句话。你能得到的最牢靠的一定得是依靠你自身实力建立起来的,而不是你所处的平台建立起来的,因为依赖平台建立起来的,离开这个平台会打折。

欢迎扫码关注我的公众号

细聊.Net Core中IServiceScope的工作方式的更多相关文章

  1. php在apache中一共有三种工作方式:CGI模式、FastCGI模式、Apache 模块DLL

    php在apache中一共有三种工作方式:CGI模式.FastCGI .FastCGI是什么? FastCGI是语言无关的.可伸缩架构的CGI开放扩展,其主要行 为是将CGI解释器进程保持在内存中并因 ...

  2. 从 python 中 axis 参数直觉解释 到 CNN 中 BatchNorm 的工作方式(Keras代码示意)

    1. python 中 axis 参数直觉解释 网络上的解释很多,有的还带图带箭头.但在高维下是画不出什么箭头的.这里阐述了 axis 参数最简洁的解释. 假设我们有矩阵a, 它的shape是(4, ...

  3. .NET Core中使用RabbitMQ正确方式

    .NET Core中使用RabbitMQ正确方式 首先甩官网:http://www.rabbitmq.com/ 然后是.NET Client链接:http://www.rabbitmq.com/dot ...

  4. Asp.Net Core中HttpClient的使用方式

    在.Net Core应用开发中,调用第三方接口也是常有的事情,HttpClient使用人数.使用频率算是最高的一种了,在.Net Core中,HttpClient的使用方式随着版本的升级也发生了一些变 ...

  5. HttpClient在.NET Core中的正确打开方式

    问题来源 长期以来,.NET开发者都通过下面的方式发送http请求: using (var httpClient = new HttpClient()) { var response = await ...

  6. asp.net core 中灵活的配置方式

    asp.net core支持外部文件和命令行参数方式来配置系统运行所需要的配置信息,我们从下面两个常用场景来具体说下具体使用方法. 一.监听地址及端口配置 1,命令行方式 asp.net core系统 ...

  7. 在Asp.Net Core中使用DI的方式使用Hangfire构建后台执行脚本

    最近项目中需要用到后台Job,原有在Windows中我们会使用命令行程序结合计划任务或者直接生成Windows Service,现在.Net Core跨平台了,虽然Linux下也有计划任务,但跟原有方 ...

  8. .Net Core 中GC的工作原理

    前言 .NET 中GC管理你服务的内存分配和释放,GC是运行公共语言运行时(CLR Common Language Runtime)中,GC可以帮助开发人员有效的分配内存和和释放内存,大多数情况下是不 ...

  9. 在ASP.NET Core中构建路由的5种方法

    原文链接 :https://stormpath.com/blog/routing-in-asp-net-core 在ASP.NET Core中构建路由的5种方法 原文链接 :https://storm ...

随机推荐

  1. 关于Google词向量模型(googlenews-vectors-negative300.bin)的导入问题

    起因 项目中有如下代码: word2vec = KeyedVectors.load_word2vec_format('./GoogleNews-vectors-negative300.bin', bi ...

  2. Docker 15 Compose

    参考源 https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from=333.999.0.0 https://www.bilibili.com/vid ...

  3. HC32L110 在 Ubuntu 下使用 J-Link 烧录

    目录 HC32L110(一) HC32L110芯片介绍和Win10下的烧录 HC32L110(二) HC32L110在Ubuntu下的烧录 HC32L110 在 Ubuntu 下使用 J-Link 烧 ...

  4. 解决使用(Jenkins检出代码)git clone检出代码提示必须安装 .NET framework,Version =v4.7.2

    一.事件背景 真的是非常想使用pipeline流水线进行自动化部署打包测试. 于是,晚上下班回家后,真的是"现学现卖",开始做流水线脚本. 经过不懈努力,熬到凌晨两点多,终于把整个 ...

  5. 关于rt-thread调度器实现的底层代码分析

      本文使用了rt-thread自带的钩子函数和显示函数进行了实验,从rt-thread自带的延时函数rt_thread_delay()函数入手,对rt-thread系统的调度器进行分析.主要参考资料 ...

  6. 在Linux下配置RealVNC和TigerVNC

    作者:alittlemc | 更新中 | 原创文章,可能有技术理解错误,欢迎指正,请与我联系,谢谢! 命令和快速总结 realvnc创建# :会话id -name 取名字 -depth 色深 -geo ...

  7. Linux之SElinux服务详解

    SElinux -> Linux安全访问策略 -> 强制性 (security安全) 是Linux操作系统的一个额外的强制性的安全访问规则.用于确定哪个进程可以访问哪些文件.目录和端口的一 ...

  8. KingbaseES 工具sys_dump,sys_restore使用介绍

    说明: KingbaseES V8R6版本中自带数据库备份导出sys_dump,和备份恢复sys_restore工具. sys_dump:把KingbaseES数据库抽取为一个脚本文件或其他归档文件. ...

  9. CDH6.2.0安装并使用基于HBase的Geomesa

    1. 查看CDH 安装的hadoop 和 hbase 对应的版本 具体可以参考以下博客: https://www.cxyzjd.com/article/spark_Streaming/10876290 ...

  10. [Python]-opencv-python模块(cv2)-图片读取和格式转换

    python常常用opencv模块来处理图像. import cv2 as cv 读取图片:imread() 默认按照彩色三通道读取: img = cv2.imread(path) 读取灰度图: im ...