[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數據,這樣的邏輯反映在上面給出的代碼片段中。

[ASP.NET Core 3框架揭秘] Options[1]: 配置選項的正確使用方式[上篇]
[ASP.NET Core 3框架揭秘] Options[2]: 配置選項的正確使用方式[下篇]
[ASP.NET Core 3框架揭秘] Options[3]: Options模型[上篇]
[ASP.NET Core 3框架揭秘] Options[4]: Options模型[下篇]
[ASP.NET Core 3框架揭秘] Options[5]: 依賴注入
[ASP.NET Core 3框架揭秘] Options[6]: 擴展與定制
[ASP.NET Core 3框架揭秘] Options[7]: 與配置系統的整合


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM