細聊.Net Core中IServiceScope的工作方式

前言

    自從.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內是如何工作的,原始點的寫法其實就是

IServiceCollection services = new ServiceCollection();
services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之間", Sex = "Man" });

IServiceProvider serviceProvider = services.BuildServiceProvider();
using (IServiceScope serviceScope = serviceProvider.CreateScope())
{
    var personOne = serviceScope.ServiceProvider.GetService<Person>();
    Console.WriteLine(person.Name);
}

如果在ASP.NET Core框架裏那玩法就多了,只要有IServiceProvide的地方都可以使用CreateScope()CreateAsyncScope()方法,簡單演示一下,但是如果感覺自己把握不住的話還是提前自己試驗一下

[HttpGet]
public async Task<object> JudgeScope([FromServices]IServiceProvider scopeProvider)
{
    using IServiceScope scope = HttpContext.RequestServices.CreateScope();
    Person person = scope.ServiceProvider.GetService<Person>();

    await using (AsyncServiceScope scope2 = scopeProvider.CreateAsyncScope())
    {
        Person person2 = scope2.ServiceProvider.GetService<Person>();
    }
    return person;
}

源碼探究

通過上面的示例,我們可以看到其實關於IServiceScope的操作部分就是三個核心。

  • 通過CreateScope()CreateAsyncScope()方法創建服務作用域。
  • 使用GetService相關的方法創建需要的對象實例。
  • 用完了作用域之後通過使用Dispose()或者DisposeAsync()方法(using的方式同理)釋放作用域。

先說AsyncServiceScope

爲了怕大家心裏有疑慮,因爲使用CreateScope()方法創建出來的是IServiceScope實例,使用CreateAsyncScope方法創建的是AsyncServiceScope實例。咱們這裏先來說一下AsyncServiceScopeIServiceScope的關係,看了之後大家就不用惦記它了,先來看一下CreateAsyncScope()方法的定義[點擊查看源碼👈]

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

方法就是返回AsyncServiceScope實例,接下來來看一下這個類的定義[點擊查看源碼👈]

public readonly struct AsyncServiceScope : IServiceScope, IAsyncDisposable
{
    private readonly IServiceScope _serviceScope;

    public AsyncServiceScope(IServiceScope serviceScope)
    {
        //AsyncServiceScope也是IServiceScope實例構建起來的
        _serviceScope = serviceScope ?? throw new ArgumentNullException(nameof(serviceScope));
    }

    //ServiceProvider也是直接在IServiceScope實例中直接獲取的
    public IServiceProvider ServiceProvider => _serviceScope.ServiceProvider;

    //同步釋放
    public void Dispose()
    {
        _serviceScope.Dispose();
    }

    //異步釋放
    public ValueTask DisposeAsync()
    {
        //因爲IAsyncDisposable的ServiceProvider能繼續創建作用域
        //使用CreateScope或CreateAsyncScope方法
        if (_serviceScope is IAsyncDisposable ad)
        {
            return ad.DisposeAsync();
        }
        _serviceScope.Dispose();

        return default;
    }
}

通過源碼我們可以看到AsyncServiceScope本身是包裝了IServiceScope實例,它本身也是實現了IServiceScope接口並且同時IAsyncDisposable接口以便可以異步調用釋放。相信大家都知道,實現了IDispose接口可以使用using IServiceScope scope = HttpContext.RequestServices.CreateScope()的方式,它編譯完之後其實是

IServiceScope scope = HttpContext.RequestServices.CreateScope();
try
{
  //具體操作
}
finally
{
    scope.Dispose();
}

實現了IAsyncDisposable接口可以使用await using (AsyncServiceScope scope2 = scopeProvider.CreateAsyncScope())的方式,它編譯完的代碼則是

AsyncServiceScope scope2 = scopeProvider.CreateAsyncScope();
try
{
  //具體操作
}
finally
{
    await scope2.DisposeAsync();
}

打消了這個疑慮,相信大家對它們的關係有了瞭解,本質就是包裝了一下IServiceScope實例。

由創建開始

接下來我們可以專心的看一下IServiceScope相關的實現,IServiceScope的創建則是來自IServiceProvider的擴展方法CreateScope(),首先看下它的定義[點擊查看源碼👈]

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

好吧,短短的一行代碼,我們可以得到兩個比較重要的信息

  • 首先獲取到的IServiceScopeFactory實例,看過上篇文章的可以知道,默認情況通過IServiceScopeFactory實例獲取的是根容器的實例。
  • 其次IServiceProvider的CreateScope擴展方法,本質是調用的IServiceScopeFactoryCreateScope方法。

接下來我們就看看下IServiceScopeFactory默認實現類中關於CreateScope()方法的定義,在ServiceProviderEngineScope類中[點擊查看源碼👈]

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

這裏毫無疑問了RootProvider屬性裏的實例都是來自根容器,而CreateScope()方法則是調用的ServiceProviderCreateScope()方法。看下ServiceProvider類的CreateScope方法定義
[點擊查看源碼👈]

private bool _disposed;
internal IServiceScope CreateScope()
{
    //判斷當前ServiceProvider是否被釋放
    if (_disposed)
    {
        //如果已經釋放則直接拋出異常
        ThrowHelper.ThrowObjectDisposedException();
    }
    //創建ServiceProviderEngineScope實例
    return new ServiceProviderEngineScope(this, isRootScope: false);
}

通過上面的代碼我們可以看到CreateScope()方法,本質是創建了一個ServiceProviderEngineScope方法實例。通過創建的這一行代碼,好巧不巧又可以得到兩個重要的信息。

  • 一是ServiceProviderEngineScope構造函數的第一個參數,傳遞的是當前的ServiceProvider實例。
  • 二是ServiceProviderEngineScope構造函數的第二個參數叫isRootScope值給的是false,說明當前ServiceProviderEngineScope實例不是根作用域,也就是我們說的子作用域。

大致看一下ServiceProviderEngineScope構造函數的實現[點擊查看源碼👈]

internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAsyncDisposable, IServiceScopeFactory
{
    internal Dictionary<ServiceCacheKey, object> ResolvedServices { get; }
    internal object Sync => ResolvedServices;
    internal ServiceProvider RootProvider { get; }
    public bool IsRootScope { get; }
    //IServiceProvider的ServiceProvider屬性則是賦值的當前實例
    public IServiceProvider ServiceProvider => this;
    public ServiceProviderEngineScope(ServiceProvider provider, bool isRootScope)
    {
        //用來存儲當前作用域管理的對象實例
        ResolvedServices = new Dictionary<ServiceCacheKey, object>();
        //創建當前實例的根容器
        RootProvider = provider;
        //標識當前作用域是否是根容器
        IsRootScope = isRootScope;
    }
}

下大致看一下,因爲接下來會涉及到ServiceProviderEngineScope這個類。到目前爲止涉及到了兩個比較重要的類ServiceProviderServiceProviderEngineScope,它們都是實現了IServiceProvider接口。看起來有點亂的樣子,不過我們可以姑且這麼理解。ServiceProvider是IServiceProvider的直系實現類,作爲IServiceProvider根容器的實現。ServiceProviderEngineScope是用於,通過IServiceProvider創建作用域時表現出來的實例,也就是非根容器的實例。

探究獲取方法

關於上面的介紹,我們其實探究了一點serviceProvider.CreateScope(),接下來我們就需要看一下關於獲取的相關操作,也就是GetService方法相關,它的使用形式是serviceScope.ServiceProvider.GetService<T>()。上面我們提到過ServiceProviderEngineScopeServiceProvider屬性實例則是當前ServiceProviderEngineScope的實例,所以我們直接去看ServiceProviderEngineScopeGetService方法[點擊查看源碼👈]

internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAsyncDisposable, IServiceScopeFactory
{
    private bool _disposed;
    internal ServiceProvider RootProvider { get; }
    public object GetService(Type serviceType)
    {
        //判斷當前實例是否釋放
        if (_disposed)
        {
            //如果已經釋放則直接拋出異常
            ThrowHelper.ThrowObjectDisposedException();
        }

        return RootProvider.GetService(serviceType, this);
    }
}

看着挺亂的,各種跳轉各種調用。不過本質只設計到兩個類ServiceProviderServiceProviderEngineScope,先說明一下,省的大家看着整蒙圈了。通過最後一句代碼,我們又能得到兩個比較重要的信息。

  • ServiceProviderEngineScope的GetService方法,本質是在調用RootProvider的GetService方法。通過前面咱們的源碼分析可以知道RootProvider屬性的值是ServiceProvider實例也就是代表的根容器。
  • 調用RootProvider的GetService方法的時候傳遞了當前ServiceProviderEngineScope實例。

接下來就可以直接找到ServiceProvider的GetService方法了,看一下里面的具體相關實現[點擊查看源碼👈]

public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDisposable
{
    private bool _disposed;
    private ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>> _realizedServices;
    private readonly Func<Type, Func<ServiceProviderEngineScope, object>> _createServiceAccessor;
    internal ServiceProviderEngine _engine;

    internal ServiceProvider()
    {
      _createServiceAccessor = CreateServiceAccessor;
      _realizedServices = new ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>>();
    }

    internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
    {
        //判斷當前實例是否釋放
        if (_disposed)
        {
            ThrowHelper.ThrowObjectDisposedException();
        }
        //緩存獲取服務實例委託的字典,值爲要獲取實例的類型,值是創建實例的委託
        //_createServiceAccessor本質是CreateServiceAccessor方法委託
        Func<ServiceProviderEngineScope, object> realizedService = _realizedServices.GetOrAdd(serviceType, _createServiceAccessor);
        OnResolve(serviceType, serviceProviderEngineScope);
        DependencyInjectionEventSource.Log.ServiceResolved(this, serviceType);
        //執行realizedService委託,傳遞的是ServiceProviderEngineScope實例
        var result = realizedService.Invoke(serviceProviderEngineScope);
        System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceType));
        return result;
    }

    private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serviceType)
    {
        //獲取ServiceCallSite,其實就是獲取要解析對象的實例相關信息
        ServiceCallSite callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
        if (callSite != null)
        {
            DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceType, callSite);
            OnCreate(callSite);
            //咱們當前討論的是Scope週期對象的問題,這個方法描述的是生命週期爲ServiceLifetime.Singleton的情況,可以跳過這個邏輯
            //如果是單例情況,則直接在根容器中直接去操作對象實例,和當前的ServiceProviderEngineScope無關
            if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
            {
                object value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
                return scope => value;
            }
            //解析ServiceCallSite裏的信息
            return _engine.RealizeService(callSite);
        }

        return _ => null;
    }
}

這裏我們看下CallSiteFactory.GetCallSite方法,先來說一下這個方法是做啥的。我們要獲取某個類型的實例(可以理解爲我們演示示例裏的Person類),但是我們註冊類相關的信息的時候(比如上面的services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之間", Sex = "Man" }))涉及到幾種方式,比如AddScoped<T>Add<T>(Func<IServiceProvider,object>),我們需要知道創建類型實例的時候使用哪種方式(比如我們的Person是使用委託的這種方式),ServiceCallSite正是存儲的類型和如何創建這個類型的工廠相關的信息。我們來看一下GetCallSite方法的核心操作[點擊查看源碼👈]

private readonly ConcurrentDictionary<ServiceCacheKey, ServiceCallSite> _callSiteCache = new ConcurrentDictionary<ServiceCacheKey, ServiceCallSite>();

private ServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot)
{
    if (serviceType == descriptor.ServiceType)
    {
        //要獲取的類型會被包裝成ServiceCacheKey
        ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceType, slot);
        //在緩存中獲取ServiceCallSite實例,可以理解爲設計模式中的享元模式
        if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite serviceCallSite))
        {
            return serviceCallSite;
        }

        ServiceCallSite callSite;
        //根據ServiceDescriptor.Lifetime包裝ResultCache
        var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
        //ServiceDescriptor就是我們添加到IServiceCollection的最終形式
        //我們註冊服務的時候本質就是在IServiceCollection裏添加ServiceDescriptor實例

        //AddScope<T>()這種形式
        if (descriptor.ImplementationInstance != null)
        {
            callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.ImplementationInstance);
        }
        //AddScope(Func<IServiceProvider,object>)形式
        else if (descriptor.ImplementationFactory != null)
        {
            callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory);
        }
        //AddScope<T,TImpl>()形式
        else if (descriptor.ImplementationType != null)
        {
            callSite = CreateConstructorCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationType, callSiteChain);
        }
        else
        {
            throw new InvalidOperationException(SR.InvalidServiceDescriptor);
        }
        //將創建的ServiceCallSite緩存起來
        return _callSiteCache[callSiteKey] = callSite;
    }
    return null;
}

而解析ServiceCallSite實例的方法RealizeService(ServiceCallSite)則是在ServiceProviderEngine類中,看一下相關實現[點擊查看源碼👈]

 public override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
{
    int callCount = 0;
    return scope =>
    {
        //核心代碼是Resolve方法,這裏的scope則是ServiceProviderEngineScope
        //即我們上面通過CreateScope()創建的實例
        var result = CallSiteRuntimeResolver.Instance.Resolve(callSite, scope);
        if (Interlocked.Increment(ref callCount) == 2)
        {
            _ = ThreadPool.UnsafeQueueUserWorkItem(_ =>
            {
                try
                {
                    _serviceProvider.ReplaceServiceAccessor(callSite, base.RealizeService(callSite));
                }
                catch (Exception ex)
                {
                   //省略掉非核心代碼
                }
            },
            null);
        }
        return result;
    };
}

上面我們看到的RealizeService()方法返回的是一個委託,而調用這個委託的地方則是上面源碼中看到的realizedService.Invoke(serviceProviderEngineScope),核心操作在CallSiteRuntimeResolver.Instance.Resolve()方法,Resolve方法的核心邏輯在VisitCallSite()方法,看一下它的實現方式[點擊查看源碼👈]

protected virtual TResult VisitCallSite(ServiceCallSite callSite, TArgument argument)
{
    if (!_stackGuard.TryEnterOnCurrentStack())
    {
        return _stackGuard.RunOnEmptyStack((c, a) => VisitCallSite(c, a), callSite, argument);
    }

    switch (callSite.Cache.Location)
    {
        //ServiceLifetime.Singleton單例情況
        case CallSiteResultCacheLocation.Root:
            return VisitRootCache(callSite, argument);
        //ServiceLifetime.Scoped作用域情況,也就是咱們關注的情況
        case CallSiteResultCacheLocation.Scope:
            return VisitScopeCache(callSite, argument);
        //ServiceLifetime.Transient瞬時情況
        case CallSiteResultCacheLocation.Dispose:
            return VisitDisposeCache(callSite, argument);
        case CallSiteResultCacheLocation.None:
            return VisitNoCache(callSite, argument);
        default:
            throw new ArgumentOutOfRangeException();
    }
}

因爲我們關注的是CallSiteResultCacheLocation.Scope這種情況所以我們重點關注的是VisitScopeCache()這段方法邏輯,CallSiteRuntimeResolver的VisitCache()方法[點擊查看源碼👈]

protected override object VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
    //咱們關注的是Scope的情況,所以重點在VisitCache方法
    return context.Scope.IsRootScope ?
        VisitRootCache(callSite, context) :
        VisitCache(callSite, context, context.Scope, RuntimeResolverLock.Scope);
}

//這裏的serviceProviderEngine參數就是我們傳遞進來的ServiceProviderEngineScope實例
private object VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
{
    bool lockTaken = false;
    //獲取ServiceProviderEngineScope的Sync屬性
    object sync = serviceProviderEngine.Sync;
    //獲取ServiceProviderEngineScope的ResolvedServices屬性
    Dictionary<ServiceCacheKey, object> resolvedServices = serviceProviderEngine.ResolvedServices;
    //加鎖
    if ((context.AcquiredLocks & lockType) == 0)
    {
        Monitor.Enter(sync, ref lockTaken);
    }

    try
    {
        //判斷ServiceProviderEngineScope的ResolvedServices的屬性裏是否包含該類型實例
        //當前作用域內只有一個實例,所以緩存起來
        if (resolvedServices.TryGetValue(callSite.Cache.Key, out object resolved))
        {
            return resolved;
        }
        
        //當前Scope沒創建過實例的話則創建
        resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
        {
            Scope = serviceProviderEngine,
            AcquiredLocks = context.AcquiredLocks | lockType
        });
        //判斷當前類型實例是否是IDispose想實例
        serviceProviderEngine.CaptureDisposable(resolved);
        //給ServiceProviderEngineScope的ResolvedServices的屬性添加緩存實例
        resolvedServices.Add(callSite.Cache.Key, resolved);
        return resolved;
    }
    finally
    {
        if (lockTaken)
        {
            Monitor.Exit(sync);
        }
    }
}

protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
{
    //比如我們上面的services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之間", Sex = "Man" })
    //對應的Kind則是CallSiteKind.Factory
    switch (callSite.Kind)
    {
        case CallSiteKind.Factory:
            //調用了VisitFactory方法
            return VisitFactory((FactoryCallSite)callSite, argument);
        case  CallSiteKind.IEnumerable:
            return VisitIEnumerable((IEnumerableCallSite)callSite, argument);
        case CallSiteKind.Constructor:
            return VisitConstructor((ConstructorCallSite)callSite, argument);
        case CallSiteKind.Constant:
            return VisitConstant((ConstantCallSite)callSite, argument);
        case CallSiteKind.ServiceProvider:
            return VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
        default:
            throw new NotSupportedException(SR.Format(SR.CallSiteTypeNotSupported, callSite.GetType()));
    }
}

protected override object VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
{
  //調用我們註冊的services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之間", Sex = "Man" })
  //FactoryCallSite的Factory即是provider => new() { Id = 1, Name = "yi念之間", Sex = "Man" }
  //context.Scope則是我們通過CreateScope創建的實例
  //返回的結果就是調用當前委託得到的實例即我們實例中的Person實例
  return factoryCallSite.Factory(context.Scope);
}

回過頭來說一下咱們上面展示的代碼,被調用執行的地方就是GetService方法裏的realizedService.Invoke(serviceProviderEngineScope)的這段代碼。上面的執行邏輯裏涉及到了ServiceProviderEngineScope裏的幾個操作,比如ResolvedServices屬性和CaptureDisposable()方法,看一下相關的代碼

internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAsyncDisposable, IServiceScopeFactory
{
    internal IList<object> Disposables => _disposables ?? (IList<object>)Array.Empty<object>();
    private bool _disposed;
    private List<object> _disposables;
    internal Dictionary<ServiceCacheKey, object> ResolvedServices { get; }
    public ServiceProviderEngineScope(ServiceProvider provider, bool isRootScope)
    {
        ResolvedServices = new Dictionary<ServiceCacheKey, object>();
    }

    internal object CaptureDisposable(object service)
    {
        //判斷實例是否實現了IDisposable或IAsyncDisposable接口,因爲這種需要在當前作用域是否的時候一起釋放
        if (ReferenceEquals(this, service) || !(service is IDisposable || service is IAsyncDisposable))
        {
            return service;
        }

        bool disposed = false;
        lock (Sync)
        {
            //判斷當前作用域是否釋放
            if (_disposed)
            {
                disposed = true;
            }
            else
            {   //如果滿足則添加到_disposables待釋放集合,以便作用域釋放的時候一起釋放
                _disposables ??= new List<object>();
                _disposables.Add(service);
            }
        }
        
        //如果當前作用域已經被釋放則直接釋放當前實例
        if (disposed)
        {
            //前提是服務實例是實現IDisposable或IAsyncDisposable接口的
            if (service is IDisposable disposable)
            {
                disposable.Dispose();
            }
            else
            {
                Task.Run(() => ((IAsyncDisposable)service).DisposeAsync().AsTask()).GetAwaiter().GetResult();
            }

            ThrowHelper.ThrowObjectDisposedException();
        }

        return service;
    }
}

上面關於ServiceProviderEngineScope類中涉及到GetService()方法的相關邏輯已經展示的差不多了,涉及到的比較多,而且看着比較亂。不過如果理解了思路還是比較清晰的,咱們來做一下一個大概的總結。

  • 首先,需要獲取ServiceCallSite,在方法GetCallSite()中,其實就是獲取要解析對象的實例相關信息。我們需要知道創建類型實例的時候使用哪種方式(比如我們的Person是使用委託的這種方式),其中也包括該對象創建的類型、創建工廠、生命週期類型。
  • 然後,得到ServiceCallSite實例之後,我們就可以通過實例創建的信息去創建信息,在方法RealizeService()裏。根據不同類型創建方式和生命週期,判斷如何創建對象,即對象存放位置。
  • 最後,如果是單例模式,則在根容器中解析這個對象,位置當然也是存儲在根容器中,全局唯一。如果是瞬時模式,則直接返回創建的對象實例,不進行任何存儲,但是需要判斷實例是否實現了IDisposable或IAsyncDisposable接口,如果是則加入當前ServiceProviderEngineScope實例的_disposables集合。如果是Scope模式就比較特殊了,因爲Scope需要在當前ServiceProviderEngineScope中存儲保證當前作用域唯一,則需要添加到ResolvedServices屬性的字典裏,同時也需要判斷是否需要添加到_disposables集合裏。

這就可以解釋ServiceProviderEngineScope針對不同生命週期的存儲方式了,單例的情況創建和存儲都是在根容器中,瞬時的情況下則每次都創建新的實例且不進行存儲,Scope的情況下則是存儲在當前的ResolvedServices中享元起來可以在當前作用域內重複使用。

關於結束釋放

前面咱們看了下關於作用域創建,在做用戶獲取對象的相關邏輯。接下來我們來看一下三件套的最後一個步驟,釋放邏輯相關的。這個邏輯比較簡單,上面咱們或多或少的也說過了一點,釋放分爲同步釋放和異步釋放兩種情況,咱們看一下同步釋放的相關實現[點擊查看源碼👈]

internal Dictionary<ServiceCacheKey, object> ResolvedServices { get; }
internal object Sync => ResolvedServices;
private bool _disposed;
private List<object> _disposables;
public void Dispose()
{
    List<object> toDispose = BeginDispose();

    if (toDispose != null)
    {
        for (int i = toDispose.Count - 1; i >= 0; i--)
        {   
            //模仿棧模式,最後創建的最先釋放
            if (toDispose[i] is IDisposable disposable)
            {
                //釋放的正式實現了IDisposable接口的對象
                disposable.Dispose();
            }
            else
            {
                throw new InvalidOperationException(SR.Format(SR.AsyncDisposableServiceDispose, TypeNameHelper.GetTypeDisplayName(toDispose[i])));
            }
        }
    }
}

private List<object> BeginDispose()
{
    //本質就是鎖住當前存儲對象的集合,不允許進行任何操作
    lock (Sync)
    {
        //如果已經釋放過了則直接返回
        if (_disposed)
        {
            return null;
        }

        DependencyInjectionEventSource.Log.ScopeDisposed(RootProvider.GetHashCode(), ResolvedServices.Count, _disposables?.Count ?? 0);
        //先把釋放標識設置了
        _disposed = true;

    }
    //判斷是否是根容器釋放
    if (IsRootScope && !RootProvider.IsDisposed())
    {
        RootProvider.Dispose();
    }

    return _disposables;
}

其實主要邏輯就是循環釋放_disposables裏的所有對象,也就是實現了IDisposable接口的對象。接下來咱們再來看一下異步釋放的相關邏輯。

public ValueTask DisposeAsync()
{
    List<object> toDispose = BeginDispose();

    if (toDispose != null)
    {
        try
        {
            for (int i = toDispose.Count - 1; i >= 0; i--)
            {
                object disposable = toDispose[i];
                //判斷是否是實現了IAsyncDisposable接口的對象
                if (disposable is IAsyncDisposable asyncDisposable)
                {
                    //獲取DisposeAsync方法返回值也就是ValueTask
                    ValueTask vt = asyncDisposable.DisposeAsync();
                    if (!vt.IsCompletedSuccessfully)
                    {
                        return Await(i, vt, toDispose);
                    }
                    //阻塞等待DisposeAsync執行完成
                    vt.GetAwaiter().GetResult();
                }
                else
                {
                    ((IDisposable)disposable).Dispose();
                }
            }
        }
        catch (Exception ex)
        {
            return new ValueTask(Task.FromException(ex));
        }
    }

    return default;

    static async ValueTask Await(int i, ValueTask vt, List<object> toDispose)
    {
        //等待DisposeAsync方法裏的邏輯執行完成
        await vt.ConfigureAwait(false);
        i--;

        for (; i >= 0; i--)
        {
            object disposable = toDispose[i];

            if (disposable is IAsyncDisposable asyncDisposable)
            {
                //等待DisposeAsync執行完成
                await asyncDisposable.DisposeAsync().ConfigureAwait(false);
            }
            else
            {
                ((IDisposable)disposable).Dispose();
            }
        }
    }
}

其實核心邏輯和同步釋放是一致的,只是IAsyncDisposable接口中的DisposeAsync()方法返回的異步相關的ValueTask所以需要進行一些等待相關的操作。不過本質都是循環釋放_disposables裏的數據,而這些數據正是當前作用域裏裏實現了IDisposable或IAsyncDisposable接口的實例。

使用CreateScope()GetService()方法的時候,都會判斷當前作用域是否釋放,而這個標識正是在Dispose()DisposeAsync()置爲true的。我們上面文章中的那個異常的引發點也正是這裏,也正是因爲作用域被釋放了表示爲置爲true了,所以GetService纔會直接拋出異常。

羣友問題解答

關於ServiceProviderEngineScope重要的相關實現,咱們上面已經大致的講解過了。其實探索它的原動力就是因爲羣友遇到的一些關於這方面的疑問,如果瞭解了它的實現的話便能輕鬆的解除心中的疑問,還原一下當時有疑問的代碼

IServiceCollection services = new ServiceCollection();
services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之間", Sex = "Man" });

IServiceProvider serviceProvider = services.BuildServiceProvider();
Person person = null;
using (IServiceScope serviceScope = serviceProvider.CreateScope())
{
    person = serviceScope.ServiceProvider.GetService<Person>();
    Console.WriteLine(person.Name);
}
if (person == null)
{
    Console.WriteLine("Person被回收了");
}

代碼大致描述的就是當時的這麼一個場景,這裏毫無疑問哈,完全判斷不出來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即垃圾回收機制。

好了到這裏差不多,歡迎大家多多交流。寒冬已至,希望大家都有禦寒的方法。分享一下看到過的一句話。你能得到的最牢靠的一定得是依靠你自身實力建立起來的,而不是你所處的平臺建立起來的,因爲依賴平臺建立起來的,離開這個平臺會打折。

👇歡迎掃碼關注我的公衆號👇
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章