[ASP.NET Core 3框架揭秘] Options[3]: Options模型[下篇]
六、IOptionsMonitorCache
IOptionsFactory解决了Options的创建与初始化问题,但由于它自身是无状态的,所以Options模型对Options对象实施缓存可以获得更好的性能。Options模型中针对Options对象的缓存由IOptionsMonitorCache对象来完成,如下所示的代码片段是该接口的定义。
public interface IOptionsMonitorCache where TOptions : class { TOptions GetOrAdd(string name, Func createOptions); bool TryAdd(string name, TOptions options); bool TryRemove(string name); void Clear(); }
由于Options模型总是根据名称来提供对应的Options对象,所以IOptionsMonitorCache对象也根据名称来缓存Options对象。如上面的代码片段所示,IOptionsMonitorCache接口提供了4个方法,分别实现针对Options缓存的获取、添加、移除和清理。IOptionsMonitorCache接口的默认实现是前面提到的OptionsCache类型,OptionsManager对象会将其作为自身的“私有”缓存。实现在OptionsCache类型中针对Options对象的缓存逻辑其实很简单:它仅仅使用一个ConcurrentDictionary<string, Lazy>对象作为缓存Options的容器而已。如下所示的代码片段基本上体现了OptionsCache类型的实现逻辑。
public class OptionsCache : IOptionsMonitorCache where TOptions : class { private readonly ConcurrentDictionary<string, Lazy> _cache = new ConcurrentDictionary<string, Lazy>(StringComparer.Ordinal); public void Clear() => _cache.Clear(); public virtual TOptions GetOrAdd(string name, Func createOptions) => _cache.GetOrAdd(name, new Lazy(createOptions)).Value; public virtual bool TryAdd(string name, TOptions options) => _cache.TryAdd(name, new Lazy(() => options)); public virtual bool TryRemove(string name) => _cache.TryRemove(name, out var ignored); }
七、IOptionsMonitor
Options模型之所以将表示缓存的接口命名为IOptionsMonitorCache,是因为缓存最初是为IOptionsMonitor对象服务的,该对象旨在实现针对承载Options对象的原始数据源的监控,并在检测到数据更新后及时替换缓存的Options对象。
public interface IOptionsMonitor { TOptions CurrentValue { get; } TOptions Get(string name); IDisposable OnChange(Action listener); }
除了直接调用定义在IOptionsMonitor接口中的OnChange方法注册应用新Options对象的回调,还可以调用如下这个同名的扩展方法。通过OnChange方法注册的回调是一个类型为Action的委托对象,由于缺少输出参数来区分Options的名称,所以注册的回调适用于所有的Options对象。值得一提的是,这两个OnChange方法的返回类型为IDisposable,实际上代表了针对回调的注册,我们可以调用返回对象的Dispose方法解除注册。
public static class OptionsMonitorExtensions { public static IDisposable OnChange( this IOptionsMonitor monitor, Action listener) => monitor.OnChange((o, _) => listener(o)); }
.NET Core应用在进行数据变化监控时总是使用一个IChangeToken对象来发送通知,用于监控Options数据变化的IOptionsMonitor对象自然也不例外。IOptionsMonitor对象在检测到数据变化后用于对外发送通知的IChangeToken对象是由一个IOptionsChangeTokenSource对象完成的。IOptionsChangeTokenSource接口的Name属性表示Options的名称,而前面所说的IChangeToken对象由其GetChangeToken方法来提供。
public interface IOptionsChangeTokenSource { string Name { get; } IChangeToken GetChangeToken(); }
Options模型定义了如下这个OptionsMonitor类型作为对IOptionsMonitor接口的默认实现。当调用构造函数创建一个OptionsMonitor对象时需要提供一个用来创建和初始化Options对象的IOptionsFactory对象,一个用来对提供的Options对象实施缓存的IOptionsMonitorCache对象,以及一组用来检测配置选项数据变化并对外发送通知的IOptionsChangeTokenSource对象。
public class OptionsMonitor :IOptionsMonitor where TOptions : class, new() { private readonly IOptionsMonitorCache _cache; private readonly IOptionsFactory _factory; private readonly IEnumerable<IOptionsChangeTokenSource> _sources; internal event Action _onChange; public OptionsMonitor(IOptionsFactory factory,IEnumerable<IOptionsChangeTokenSource> sources,IOptionsMonitorCache cache) { _factory = factory; _sources = sources; _cache = cache; foreach (var source in _sources) { ChangeToken.OnChange(() => source.GetChangeToken(),(name) => InvokeChanged(name),source.Name); } } private void InvokeChanged(string name) { name = name ?? Options.DefaultName; _cache.TryRemove(name); var options = Get(name); if (_onChange != null) { _onChange.Invoke(options, name); } } public TOptions CurrentValue { get => Get(Options.DefaultName); } public virtual TOptions Get(string name) => _cache.GetOrAdd(name, () => _factory.Create(name)); public IDisposable OnChange(Action listener) { var disposable = new ChangeTrackerDisposable(this, listener); _onChange += disposable.OnChange; return disposable; } internal class ChangeTrackerDisposable : IDisposable { private readonly Action _listener; private readonly OptionsMonitor _monitor; public ChangeTrackerDisposable(OptionsMonitor monitor, Action listener) { _listener = listener; _monitor = monitor; } public void OnChange(TOptions options, string name) => _listener.Invoke(options, name); public void Dispose() => _monitor._onChange -= OnChange; } }
由于OptionsMonitor对象提供的Options对象总是来源于IOptionsMonitorCache对象表示的缓存容器,所以它只需要利用提供的IOptionsChangeTokenSource对象来监控Options数据的变化,并在检测到变化之后及时删除缓存中对应的Options对象,这样就能保证其CurrentValue属性和Get方法返回的总是最新的Options数据,这样的逻辑反映在上面给出的代码片段中。