.NET Core 依赖注入改造(5)- Context
.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的创建,正如上面所说,涉及到了三个对象IServiceProvider
,IServiceScopeFactory
和IServiceScope
:
从
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
的生命周期
四、
正确的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 无法确定
}
五、
了解了上面这些东西之后,自己要做一个服务上下文还是比较简单的,首先分别创建IServiceProvider
,IServiceScopeFactory
和IServiceScope
3个对象的装饰类,在不改变原有逻辑的基础上,增加新的行为。
在装饰类中拓展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