[ASP.NET Core 3框架揭秘] Options[4]: Options模型[下篇]

Options[4]: Options模型[下篇] - 图1IOptionsFactory解决了Options的创建与初始化问题,但由于它自身是无状态的,所以Options模型对Options对象实施缓存可以获得更好的性能。Options模型中针对Options对象的缓存由IOptionsMonitorCache对象来完成。Options模型之所以将表示缓存的接口命名为IOptionsMonitorCache,是因为缓存最初是为IOptionsMonitor对象服务的,该对象旨在实现针对承载Options对象的原始数据源的监控,并在检测到数据更新后及时替换缓存的Options对象。

六、IOptionsMonitorCache<TOptions>

IOptionsFactory<TOptions>解决了Options的创建与初始化问题,但由于它自身是无状态的,所以Options模型对Options对象实施缓存可以获得更好的性能。Options模型中针对Options对象的缓存由IOptionsMonitorCache<TOptions>对象来完成,如下所示的代码片段是该接口的定义。

  1. public interface IOptionsMonitorCache<TOptions> where TOptions : class
  2. {
  3. TOptions GetOrAdd(string name, Func<TOptions> createOptions);
  4. bool TryAdd(string name, TOptions options);
  5. bool TryRemove(string name);
  6. void Clear();
  7. }

由于Options模型总是根据名称来提供对应的Options对象,所以IOptionsMonitorCache<TOptions>对象也根据名称来缓存Options对象。如上面的代码片段所示,IOptionsMonitorCache<TOptions>接口提供了4个方法,分别实现针对Options缓存的获取、添加、移除和清理。IOptionsMonitorCache<TOptions>接口的默认实现是前面提到的OptionsCache<TOptions>类型,OptionsManager对象会将其作为自身的“私有”缓存。实现在OptionsCache<TOptions>类型中针对Options对象的缓存逻辑其实很简单:它仅仅使用一个ConcurrentDictionary<string, Lazy<TOptions>>对象作为缓存Options的容器而已。如下所示的代码片段基本上体现了OptionsCache<TOptions>类型的实现逻辑。

  1. public class OptionsCache<TOptions> : IOptionsMonitorCache<TOptions>
  2. where TOptions : class
  3. {
  4. private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal);
  5. public void Clear() => _cache.Clear();
  6. public virtual TOptions GetOrAdd(string name, Func<TOptions> createOptions) => _cache.GetOrAdd(name, new Lazy<TOptions>(createOptions)).Value;
  7. public virtual bool TryAdd(string name, TOptions options) => _cache.TryAdd(name, new Lazy<TOptions>(() => options));
  8. public virtual bool TryRemove(string name) => _cache.TryRemove(name, out var ignored);
  9. }

七、IOptionsMonitor<TOptions>

Options模型之所以将表示缓存的接口命名为IOptionsMonitorCache<TOptions>,是因为缓存最初是为IOptionsMonitor<TOptions>对象服务的,该对象旨在实现针对承载Options对象的原始数据源的监控,并在检测到数据更新后及时替换缓存的Options对象。

  1. public interface IOptionsMonitor<out TOptions>
  2. {
  3. TOptions CurrentValue { get; }
  4. TOptions Get(string name);
  5. IDisposable OnChange(Action<TOptions, string> listener);
  6. }

除了直接调用定义在IOptionsMonitor<TOptions>接口中的OnChange方法注册应用新Options对象的回调,还可以调用如下这个同名的扩展方法。通过OnChange方法注册的回调是一个类型为Action<TOptions>的委托对象,由于缺少输出参数来区分Options的名称,所以注册的回调适用于所有的Options对象。值得一提的是,这两个OnChange方法的返回类型为IDisposable,实际上代表了针对回调的注册,我们可以调用返回对象的Dispose方法解除注册。

  1. public static class OptionsMonitorExtensions
  2. {
  3. public static IDisposable OnChange<TOptions>( this IOptionsMonitor<TOptions> monitor, Action<TOptions> listener)
  4. => monitor.OnChange((o, _) => listener(o));
  5. }

.NET Core应用在进行数据变化监控时总是使用一个IChangeToken对象来发送通知,用于监控Options数据变化的IOptionsMonitor<TOptions>对象自然也不例外。IOptionsMonitor<TOptions>对象在检测到数据变化后用于对外发送通知的IChangeToken对象是由一个IOptionsChangeTokenSource<TOptions>对象完成的。IOptionsChangeTokenSource<TOptions>接口的Name属性表示Options的名称,而前面所说的IChangeToken对象由其GetChangeToken方法来提供。

  1. public interface IOptionsChangeTokenSource<out TOptions>
  2. {
  3. string Name { get; }
  4. IChangeToken GetChangeToken();
  5. }

Options模型定义了如下这个OptionsMonitor<TOptions>类型作为对IOptionsMonitor<TOptions>接口的默认实现。当调用构造函数创建一个OptionsMonitor<TOptions>对象时需要提供一个用来创建和初始化Options对象的IOptionsFactory<TOptions>对象,一个用来对提供的Options对象实施缓存的IOptionsMonitorCache<TOptions>对象,以及一组用来检测配置选项数据变化并对外发送通知的IOptionsChangeTokenSource<TOptions>对象。

  1. public class OptionsMonitor<TOptions> :IOptionsMonitor<TOptions> where TOptions : class, new()
  2. {
  3. private readonly IOptionsMonitorCache<TOptions> _cache;
  4. private readonly IOptionsFactory<TOptions> _factory;
  5. private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources;
  6. internal event Action<TOptions, string> _onChange;
  7.  
  8. public OptionsMonitor(IOptionsFactory<TOptions> factory,IEnumerable<IOptionsChangeTokenSource<TOptions>> sources,IOptionsMonitorCache<TOptions> cache)
  9. {
  10. _factory = factory;
  11. _sources = sources;
  12. _cache = cache;
  13.  
  14. foreach (var source in _sources)
  15. {
  16. ChangeToken.OnChange<string>(() => source.GetChangeToken(),(name) => InvokeChanged(name),source.Name);
  17. }
  18. }
  19.  
  20. private void InvokeChanged(string name)
  21. {
  22. name = name ?? Options.DefaultName;
  23. _cache.TryRemove(name);
  24. var options = Get(name);
  25. if (_onChange != null)
  26. {
  27. _onChange.Invoke(options, name);
  28. }
  29. }
  30.  
  31. public TOptions CurrentValue { get => Get(Options.DefaultName); }
  32.  
  33. public virtual TOptions Get(string name) => _cache.GetOrAdd(name, () => _factory.Create(name));
  34.  
  35. public IDisposable OnChange(Action<TOptions, string> listener)
  36. {
  37. var disposable = new ChangeTrackerDisposable(this, listener);
  38. _onChange += disposable.OnChange;
  39. return disposable;
  40. }
  41.  
  42. internal class ChangeTrackerDisposable : IDisposable
  43. {
  44. private readonly Action<TOptions, string> _listener;
  45. private readonly OptionsMonitor<TOptions> _monitor;
  46.  
  47. public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
  48. {
  49. _listener = listener;
  50. _monitor = monitor;
  51. }
  52.  
  53. public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);
  54. public void Dispose() => _monitor._onChange -= OnChange;
  55. }
  56. }

由于OptionsMonitor<TOptions>对象提供的Options对象总是来源于IOptionsMonitorCache<TOptions>对象表示的缓存容器,所以它只需要利用提供的IOptionsChangeTokenSource对象来监控Options数据的变化,并在检测到变化之后及时删除缓存中对应的Options对象,这样就能保证其CurrentValue属性和Get方法返回的总是最新的Options数据,这样的逻辑反映在上面给出的代码片段中。

Options[4]: Options模型[下篇] - 图2

作者:蒋金楠微信公众账号:大内老A微博:www.weibo.com/artech如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号)。本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文:https://www.cnblogs.com/artech/p/inside-asp-net-core-06-04.html