专栏首页大内老A[ASP.NET Core 3框架揭秘] Options[4]: Options模型[下篇]

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

六、IOptionsMonitorCache<TOptions>

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

public interface IOptionsMonitorCache<TOptions> where TOptions : class
{
    TOptions GetOrAdd(string name, Func<TOptions> createOptions);
    bool TryAdd(string name, TOptions options);
    bool TryRemove(string name);
    void Clear();
}

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

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

七、IOptionsMonitor<TOptions>

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

public interface IOptionsMonitor<out TOptions>
{
    TOptions CurrentValue { get; }
    TOptions Get(string name);
    IDisposable OnChange(Action<TOptions, string> listener);
}

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

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

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

public interface IOptionsChangeTokenSource<out TOptions>
{
    string Name { get; }
    IChangeToken GetChangeToken(); 
}

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

public class OptionsMonitor<TOptions> :IOptionsMonitor<TOptions> where TOptions : class, new()
{
    private readonly IOptionsMonitorCache<TOptions> _cache;
    private readonly IOptionsFactory<TOptions> _factory;
    private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources;
    internal event Action<TOptions, string> _onChange;

    public OptionsMonitor(IOptionsFactory<TOptions> factory,IEnumerable<IOptionsChangeTokenSource<TOptions>> sources,IOptionsMonitorCache<TOptions> cache)
    {
        _factory = factory;
        _sources = sources;
        _cache = cache;

        foreach (var source in _sources)
        {
            ChangeToken.OnChange<string>(() => 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<TOptions, string> listener)
    {
        var disposable = new ChangeTrackerDisposable(this, listener);
        _onChange += disposable.OnChange;
        return disposable;
    }

    internal class ChangeTrackerDisposable : IDisposable
    {
        private readonly Action<TOptions, string> _listener;
        private readonly OptionsMonitor<TOptions> _monitor;

        public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
        {
            _listener = listener;
            _monitor = monitor;
        }

        public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);
        public void Dispose() => _monitor._onChange -= OnChange;
    }
}

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

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

    通过前面演示的几个实例(配置选项的正确使用方式[上篇]、配置选项的正确使用方式[下篇]),我们已经对基于Options的编程方式有了一定程度的了解,下面从设计的...

    蒋金楠
  • ASP.NET Core的配置(3): 将配置绑定为对象[下篇]

    我们在《读取配置信息》通过实例的形式演示了如何利用Options模型以依赖注入的方式直接获取由指定配置节绑定生成的Options对象,我们再次回顾一下当初我们编...

    蒋金楠
  • .NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象

    配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置。值得推荐的做法就是采用《.NET Cor...

    蒋金楠
  • .Net Core中的Options使用以及源码解析

    在.Net Core中引入了Options这一使用配置方式,通常来讲我们会把所需要的配置通过IConfiguration对象配置成一个普通的类,并且习惯上我们会...

    Edison.Ma
  • 44.python bytes函数

    python bytes对于刚接触python的小伙伴来讲,可能还是有点陌生!bytes是字节序列,值得注意的是它有取值范围:0 <= bytes <= 255...

    猿说编程[Python和C]
  • 浅谈 InnoDB 事务

    “傲是对付敌人的武器,特别是对弱小对手的一种气势压制,这种武器也有使用场景,不应常用。尽量看清自己,尽量看清大势,并不是你本人有多牛,只不过你有这个机会站在那个...

    搬砖俱乐部
  • 【Java案例】打印杨辉三角

    根据输入行数,打印出杨辉三角形,如图1.10所示。 ? 图1.10 杨辉三角形 案例分析 观察杨辉三角形的图案,可以发现其中的规律:三角形的竖边和斜边都是“1...

    奋斗蒙
  • “序列分析基础”是什么

    小伙伴的某个考试有少部分生物信息学内容,她没涉及过这些内容。但有个大概复习大纲,其中有一些题目。因为时间紧迫,我就直接给她讲了讲。但是有一个题,题目是《序列分析...

    Y大宽
  • 带你轻松打开SVG动画的大门

    初学SVG的时候,感觉那一坨一坨的代码难读难懂,现在回过头仔细想想,是因为那时候看文档缺少一些具体的实例,导致学习起来很枯燥。如今SVG已经在前端各个领域都有所...

    练小习
  • 带你轻松打开SVG动画的大门

    初学SVG的时候,感觉那一坨一坨的代码难读难懂,现在回过头仔细想想,是因为那时候看文档缺少一些具体的实例,导致学习起来很枯燥。如今SVG已经在前端各个领域都有所...

    练小习

扫码关注云+社区

领取腾讯云代金券