1. 程式人生 > >[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資料,這樣的邏輯反映在上面給出的程式碼片段中。

[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]: 與配置系統的整合