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