美文网首页.NET
.NET Core 依赖注入改造(5)- Context

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

作者: 冰麟轻武 | 来源:发表于2018-09-16 17:56 被阅读45次

    .NET Core 依赖注入改造(1)- 命名服务
    .NET Core 依赖注入改造(2)- 委托转换
    .NET Core 依赖注入改造(3)- ILogger
    .NET Core 依赖注入改造(4)- ActivatorUtilities
    .NET Core 依赖注入改造(5)- Context

    .NET Core 依赖注入改造(附1)- Autowrite

    一、

    一定有人跟我一样想过,在任何时候都可以轻易的得到一个IServerProvider
    在Web项目中我们可以通过HttpContext.RequestServices来获取,但是其他项目目前官方还没有这样的上下文对象可用;
    所以老规矩自己改造一个。

    二、

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

    ServiceScope为某个ServiceProvider对象圈定了一个“作用域”,枚举类型ServiceLifetime中的Scoped选项指的就是这么一个ServiceScope。在依赖注入的应用编程接口中,ServiceScope通过一个名为IServiceScope的接口来表示。如下面的代码片段所示,继承自IDisposable接口的IServiceScope具有一个唯一的只读属性ServiceProvider返回确定这个服务范围边界的ServiceProvider。表示ServiceScope由它对应的工厂ServiceScopeFactory来创建,后者体现为具有如下定义的接口IServiceScopeFactory。

    摘自:artech

    三、

    Scope的创建,正如上面所说,涉及到了三个对象IServiceProviderIServiceScopeFactoryIServiceScope

    IServiceProvider 中获取 IServiceScopeFactory 服务,创建 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使用方式应该是有层级的;
    同一线程同一作用域中同一层级Scope应该只有一个
    如:

    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 无法确定
    }
    

    五、

    了解了上面这些东西之后,自己要做一个服务上下文还是比较简单的,首先分别创建IServiceProviderIServiceScopeFactoryIServiceScope3个对象的装饰类,在不改变原有逻辑的基础上,增加新的行为
    在装饰类中拓展Scope的创建和销毁行为,创建时将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);  // 设置到上下文
        }
        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 设置到上下文,另外对IServiceScopeFactory服务进行装饰

    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,并在销毁时调用SupportContextServiceProvider.Dispose

    六、

    现在就剩下一个上下文对象 ServiceContext,这个对象比较复杂,所以放到最后来再讲;
    首先,在.net core中有一个对象是专门用来处理类似“上下文”这种需求的AsyncLocal<T>

    基于任务的异步编程模型倾向于抽象的线程,使用AsyncLocal<T>实例可用于跨线程保存数据。

    但是考虑跨线程销毁Scope的情况(虽然使用中需要避免这种情况),但代码还是要严谨;
    所以不能直接使用AsyncLocal<IServiceProvider>
    使用一个ServiceProviderAccessor来访问;
    而这个ServiceProviderAccessor只做一件事,当provider被标识为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方法是当AsyncLocal<T>值发生变更时被调用的;
    其中obj.ThreadContextChanged用于指示是否是由于上下文切换引起的值改变;
    当因为线程切换发生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);
    }
    

    与刚才那个是一回事;
    再来体会下这句话

    当因为线程切换发生Scope变更时,如果前一个Scope还没有销毁,那么就带回来

    ServiceContextFactory

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

    七、

    在 Core Web 中测试一下:
    先在 Startup.ConfigureServices 创建支持上下文的服务提供程序

    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"};
    }
    

    结果


    true

    八、

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

    相关文章

      网友评论

        本文标题:.NET Core 依赖注入改造(5)- Context

        本文链接:https://www.haomeiwen.com/subject/twxfnftx.html