.NET Core 依賴注入改造(5)- Context

.NET Core 依賴注入改造(1)- 命名服務(wù)
.NET Core 依賴注入改造(2)- 委托轉(zhuǎn)換
.NET Core 依賴注入改造(3)- ILogger
.NET Core 依賴注入改造(4)- ActivatorUtilities
.NET Core 依賴注入改造(5)- Context

.NET Core 依賴注入改造(附1)- Autowired

一、

一定有人跟我一樣想過,在任何時候都可以輕易的得到一個IServerProvider;
在Web項目中我們可以通過HttpContext.RequestServices來獲取,但是其他項目目前官方還沒有這樣的上下文對象可用;
所以老規(guī)矩自己改造一個。

二、

真正改造之前,需要了解一下:Scope。
Scope可以理解為作用域;

ServiceScope為某個ServiceProvider對象圈定了一個“作用域”,枚舉類型ServiceLifetime中的Scoped選項指的就是這么一個ServiceScope。在依賴注入的應(yīng)用編程接口中,ServiceScope通過一個名為IServiceScope的接口來表示。如下面的代碼片段所示,繼承自IDisposable接口的IServiceScope具有一個唯一的只讀屬性ServiceProvider返回確定這個服務(wù)范圍邊界的ServiceProvider。表示ServiceScope由它對應(yīng)的工廠ServiceScopeFactory來創(chuàng)建,后者體現(xiàn)為具有如下定義的接口IServiceScopeFactory。

摘自:artech

三、

Scope的創(chuàng)建,正如上面所說,涉及到了三個對象IServiceProvider,IServiceScopeFactoryIServiceScope

IServiceProvider 中獲取 IServiceScopeFactory 服務(wù),創(chuàng)建 IServiceScope,從Scope中得到限定作用域的 IServiceProvider

用代碼來表示就是:

IServiceProvider serviceProvider = ...;
var factory = (IServiceScopeFactory)serviceProvider.GetService(typeof(IServiceScopeFactory));
using(var scope = factory.CreateScope())
{
    var provider = scope.ServiceProvider;
}

ps:IServiceScope同時也是一個IDispose對象,這是非常重要的,這可以使我們方便的跟蹤IServiceScope生命周期

Microsoft.Extensions.DependencyInjection擴展方法

四、

正確的Scope使用方式應(yīng)該是有層級的;
同一線程同一作用域中同一層級Scope應(yīng)該只有一個
如:

using (var scope1 = _provider.CreateScope())
{
    using (var scope2 = scope1.ServiceProvider.CreateScope())
    {
        Parallel.For(0, 10, i => // 異步時,不同線程可以存在同一層級的Scope
        {
            using (var scope3 = scope2.ServiceProvider.CreateScope())
            {
                using (var scope4 = scope3.ServiceProvider.CreateScope())
                {

                }
            }
        });
    }
}

下面這種用法是錯的:scope1/2/3/4都屬于同一層級;

using (var scope1 = _provider.CreateScope())
using (var scope2 = _provider.CreateScope())
using (var scope3 = _provider.CreateScope())
using (var scope4 = _provider.CreateScope())
{
    action(scope1);
    action(scope2);
    action(scope3);
    action(scope4);
    // 在這種情況下,上下文中存在多個同級作用域, Scope 無法確定
}

五、

了解了上面這些東西之后,自己要做一個服務(wù)上下文還是比較簡單的,首先分別創(chuàng)建IServiceProvider,IServiceScopeFactoryIServiceScope3個對象的裝飾類,在不改變原有邏輯的基礎(chǔ)上,增加新的行為。
在裝飾類中拓展Scope的創(chuàng)建和銷毀行為,創(chuàng)建時將Scope中的IServiceProvider放到上下文中,在Scope銷毀時,從上下文中移除,并將之前的IServiceProvider重新放進去。

源碼在這里
下面摘取部分重要代碼

IServiceProvider裝飾類

class SupportContextServiceProvider : IServiceProvider
{
    private readonly IServiceProvider _provider;
    public SupportContextServiceProvider Parent { get; }
    public SupportContextServiceProvider Root { get; }
    public SupportContextServiceProvider(IServiceProvider provider, SupportContextServiceProvider parent)
    {
        _provider = provider ?? throw new ArgumentNullException(nameof(provider));
        Parent = parent;
        Root = parent?.Root ?? this;
        ServiceContext.Push(this);  // 設(shè)置到上下文
    }
    private int _disposed;
    public bool IsDisposed => _disposed > 0;
    internal void Dispose()
    {
        if (_disposed == 0 && Interlocked.Increment(ref _disposed) == 1)
        {
            ServiceContext.PopTo(Parent);  // 重置上下文到上一層
        }
    }

    public object GetService(Type serviceType)
    {
        var value = _provider.GetService(serviceType);
        if (value is IServiceScopeFactory factory)
        {
            return new SupportContextServiceScopeFactory(this, factory); // 裝飾Factory
        }
        if (ReferenceEquals(value, _provider))
        {
            return this; // 裝到底
        }
        return value;
    }
}

這個類主要用于將 IServiceProvider 設(shè)置到上下文,另外對IServiceScopeFactory服務(wù)進行裝飾

IServiceScopeFactory裝飾類

class SupportContextServiceScopeFactory : IServiceScopeFactory
{
    private readonly SupportContextServiceProvider _provider;
    private readonly IServiceScopeFactory _factory;

    public SupportContextServiceScopeFactory(SupportContextServiceProvider provider, IServiceScopeFactory factory)
    {
        _provider = provider ?? throw new ArgumentNullException(nameof(provider));
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
    }

    public IServiceScope CreateScope() => new SupportContextServiceScope(_provider, _factory.CreateScope());
}

這個類只做一件事,裝飾IServiceScope

IServiceScope 裝飾類

class SupportContextServiceScope : IServiceScope
{
    private readonly IServiceScope _scope;

    public SupportContextServiceScope(SupportContextServiceProvider parent, IServiceScope scope)
    {
        if (parent == null)
        {
            throw new ArgumentNullException(nameof(parent));
        }
        _scope = scope ?? throw new ArgumentNullException(nameof(scope));
        ServiceProvider = new SupportContextServiceProvider(scope.ServiceProvider, parent);
    }

    public IServiceProvider ServiceProvider { get; }

    public void Dispose()
    {
        _scope.Dispose();
        ((SupportContextServiceProvider)ServiceProvider).Dispose();
    }
    ~SupportContextServiceScope()
    {
        ((SupportContextServiceProvider)ServiceProvider).Dispose();
    }
}

做2件事,裝飾IServiceProvider,并在銷毀時調(diào)用SupportContextServiceProvider.Dispose

六、

現(xiàn)在就剩下一個上下文對象 ServiceContext,這個對象比較復(fù)雜,所以放到最后來再講;
首先,在.net core中有一個對象是專門用來處理類似“上下文”這種需求的AsyncLocal<T>;

基于任務(wù)的異步編程模型傾向于抽象的線程,使用AsyncLocal<T>實例可用于跨線程保存數(shù)據(jù)。

但是考慮跨線程銷毀Scope的情況(雖然使用中需要避免這種情況),但代碼還是要嚴(yán)謹(jǐn);
所以不能直接使用AsyncLocal<IServiceProvider>;
使用一個ServiceProviderAccessor來訪問;
而這個ServiceProviderAccessor只做一件事,當(dāng)provider被標(biāo)識為IsDisposed時返回provider.Parent

[DebuggerDisplay("{DebugText}")]
class ServiceProviderAccessor
{
    public ServiceProviderAccessor(SupportContextServiceProvider provider) => _provider = provider;

    private SupportContextServiceProvider _provider;

    internal SupportContextServiceProvider Provider
    {
        get
        {
            var current = _provider;
            while (current?.IsDisposed == true)
            {
                _provider = current = current.Parent;
            }
            return current;
        }
    }

    private string DebugText() => 
        $"Provider: {_provider}{(_provider?.IsDisposed == true ? " (disposed)" : "")}";
}

他的初始化就放在IServiceProvider裝飾類里;

class SupportContextServiceProvider : IServiceProvider
{
    private SupportContextServiceProvider() => Accessor = new ServiceProviderAccessor(this);
    internal ServiceProviderAccessor Accessor { get; }
}

ServiceContext 上下文

public static class ServiceContext
{
    private static AsyncLocal<ServiceProviderAccessor> _value = 
               new AsyncLocal<ServiceProviderAccessor>(LocalValueChanged);

    public static IServiceProvider Provider => _value.Value?.Provider;

    private static SupportContextServiceProvider ProviderImpl
    {
        get => _value.Value?.Provider;
        set
        {
            var accessor = value.Accessor;
            if (!ReferenceEquals(accessor, _value.Value))
            {
                _value.Value = value.Accessor;
            }
        }
    }
    internal static void Push(SupportContextServiceProvider provider)
    {
        if (provider != null)
        {
            ProviderImpl = provider;
        }
    }
    internal static bool PopTo(SupportContextServiceProvider provider)
    {
        provider = provider.Accessor.Provider;
        if (provider != null)
        {
            ProviderImpl = provider;
        }
    }

    private static void LocalValueChanged(AsyncLocalValueChangedArgs<ServiceProviderAccessor> obj)
    {
        if (obj.ThreadContextChanged)
        {
            var prev = obj.PreviousValue?.Provider;
            var curr = obj.CurrentValue?.Provider;
            if (curr == null || prev?.IsDisposed == false)
            {
                ProviderImpl = prev;
            }
        }
    }
}

LocalValueChanged方法是當(dāng)AsyncLocal<T>值發(fā)生變更時被調(diào)用的;
其中obj.ThreadContextChanged用于指示是否是由于上下文切換引起的值改變;
當(dāng)因為線程切換發(fā)生Scope變更時,如果前一個Scope還沒有銷毀,那么就帶回來;
為了處理類似這種情況:

IServiceProvider provider = ...;
IServiceScope scope; //上下文 = provider
await Task.Run(() =>
{
    scope = provider.CreateScope(); // 上下文 = scope
});
action(scope); // 上下文 = provider (這里顯然是錯的)
scope.Dispose();

有看官可能會說了,哪有人寫這樣的代碼...
那我給他換個樣子:

IServiceProvider provider = ...;
using (IServiceScope scope = await CreateScopeAsync(provider))
{
    action(scope);
}

與剛才那個是一回事;
再來體會下這句話

當(dāng)因為線程切換發(fā)生Scope變更時,如果前一個Scope還沒有銷毀,那么就帶回來

ServiceContextFactory

public static class ServiceContextFactory
{
    public static IServiceProvider Create(IServiceProvider provider) =>
        new SupportContextServiceProvider(provider, null);
}

七、

在 Core Web 中測試一下:
先在 Startup.ConfigureServices 創(chuàng)建支持上下文的服務(wù)提供程序

public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        return ServiceContextFactory.Create(services.BuildServiceProvider());
    }
}

然后在Controller中驗證下

[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
    var b = ReferenceEquals(HttpContext.RequestServices, ServiceContext.Provider);
    return new string[] { "value1", "value2"};
}

結(jié)果


true

八、

github:https://github.com/blqw/blqw.DI/tree/master/src/blqw.DI.Context
nuget:https://www.nuget.org/packages/blqw.DI.Context

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容