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