01

IMonitorOptionsCache

IOptionsFactory<TOptions>解决了Options的创建与初始化的问题,但是由于它自身是无状态的,所以Options模型对Options对象实施缓存来获得更好的性能。在Options模型中,用于缓存Options的对象由具有如下定义的IOptionsMonitorCache<TOptions>接口来表示。

public interface IOptionsMonitorCache<TOptions> 
    here 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>接口提供了四个方法分别实现针对Options缓存的添加、移除和清除。

IMonitorOptionsCache接口在Options模型中的默认实现为前面提到过的OptionsCache<TOptions>类型,OptionsManger正是使用它来作为自身的私有缓存。实现在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);
}

02

IOptionsMonitor

Options模型之所以将表示缓存的接口命名为IMonitorOptionsCache<TOptions>,是因为缓存最初是为IOptionsMonitor<TOptions>服务的,后者旨在实现针对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的。

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

IOptionsMonitor<TOptions>接口提供的CurrentValue和Get方法用于提取当前的Options对象,前者获取的是具有默认名称(空字符串)的Options对象。我们可以调用其OnChange方法注册一个Action<TOptions, string>类型的委托对象来感知Options数据的变化,并将新的Options对象及时应用到程序之中。

值得一提的是,这个OnChange方法的返回类型为IDisposable,它实际上代表了针对回调的注册,我们可以调用返回对象的Dispose方法减除注册。

我们知道.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>对象,和一组用来检测Options数据变化并对外发送通知的IOptionsChangeTokenSource<TOptions>对象。

public class OptionsMonitor<TOptions> : 
    IOptionsMonitor<TOptions> 
    where TOptions : classnew()
{

    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数据,这样的逻辑反映在上面给出的代码片段中。

https://mp.weixin.qq.com/s/TbXl4FCFWzuyMS13axMSMA