[ASP.NET Core 3フレームワークの公開]Options[4]:Optionsモデル[下編]
13010 ワード
六、IOptionsMonitorCache
OptionsFactoryは、Optionsの作成と初期化の問題を解決しますが、それ自体が無状態であるため、OptionsモデルがOptionsオブジェクトにキャッシュを実装すると、より良いパフォーマンスが得られます.OptionsモデルのOptionsオブジェクトに対するキャッシュは、次のようなコードクリップがインタフェースの定義であるIOptionsMonitorCacheオブジェクトによって行われます.public interface IOptionsMonitorCache where TOptions : class
{
TOptions GetOrAdd(string name, Func createOptions);
bool TryAdd(string name, TOptions options);
bool TryRemove(string name);
void Clear();
}
Optionsモデルは常に名前に基づいて対応するOptionsオブジェクトを提供するため、OptionsMonitorCacheオブジェクトも名前に基づいてOptionsオブジェクトをキャッシュします.上記のコードフラグメントに示すように、OptionsMonitorCacheインタフェースは、Optionsキャッシュの取得、追加、削除、およびクリーンアップをそれぞれ実現する4つの方法を提供します.OptionsMonitorCacheインタフェースのデフォルトインプリメンテーションは、前述のOptionsCacheタイプであり、OptionsManagerオブジェクトは独自の「プライベート」キャッシュとして使用されます.OptionsCacheタイプでOptionsオブジェクトに対するキャッシュロジックを実装するのは簡単です.OptionsをキャッシュするコンテナとしてConcurrentDictionary>オブジェクトを1つだけ使用します.以下に示すコードフラグメントは、基本的にOptionsCacheタイプの実装ロジックを表す.public class OptionsCache : IOptionsMonitorCache
where TOptions : class
{
private readonly ConcurrentDictionary<string, Lazy> _cache = new ConcurrentDictionary<string, Lazy>(StringComparer.Ordinal);
public void Clear() => _cache.Clear();
public virtual TOptions GetOrAdd(string name, Func createOptions) => _cache.GetOrAdd(name, new Lazy(createOptions)).Value;
public virtual bool TryAdd(string name, TOptions options) => _cache.TryAdd(name, new Lazy(() => options));
public virtual bool TryRemove(string name) => _cache.TryRemove(name, out var ignored);
}
七、IOptionsMonitor
Optionsモデルがキャッシュを表すインタフェースをIOptionsMonitorCacheと命名したのは、キャッシュが最初にOptionsMonitorオブジェクトサービスであるためであり、このオブジェクトはOptionsオブジェクトを担持する元のデータソースの監視を実現し、データ更新が検出された後にキャッシュされたOptionsオブジェクトをタイムリーに置き換えることを目的としている.public interface IOptionsMonitor<out TOptions>
{
TOptions CurrentValue { get; }
TOptions Get(string name);
IDisposable OnChange(Actionstring> listener);
}
IOptionsMonitorインタフェースで定義されたOnChangeメソッドを直接呼び出して新しいOptionsオブジェクトを適用するコールバックを登録するだけでなく、次のような同名の拡張メソッドを呼び出すこともできます.OnChangeメソッドで登録されているコールバックは、Actionのタイプの依頼オブジェクトであり、出力パラメータが欠けているためOptionsの名前が区別されるため、登録されているコールバックはすべてのOptionsオブジェクトに適用されます.なお,この2つのOnChangeメソッドの戻りタイプはIDisposableであり,実際にはコールバックの登録を表しており,戻りオブジェクトのDisposeメソッドを呼び出して登録を解除することができる.public static class OptionsMonitorExtensions
{
public static IDisposable OnChange( this IOptionsMonitor monitor, Action listener)
=> monitor.OnChange((o, _) => listener(o));
}
.NET Coreアプリケーションは、常にIChangeTokenオブジェクトを使用して通知を送信します.Optionsデータの変化を監視するOptionsMonitorオブジェクトも例外ではありません.IOptionsMonitorオブジェクトが、データの変化を検出した後に通知を外部に送信するために使用されるIChangeTokenオブジェクトは、IOptionsChangeTokenSourceオブジェクトによって作成されます.OptionsChangeTokenSourceインタフェースのNameプロパティはOptionsの名前を表し、前述のIChangeTokenオブジェクトはGetChangeTokenメソッドによって提供されます.public interface IOptionsChangeTokenSource<out TOptions>
{
string Name { get; }
IChangeToken GetChangeToken();
}
Optionsモデルは、OptionsMonitorインタフェースのデフォルト実装として、次のようなOptionsMonitorタイプを定義します.コンストラクション関数を呼び出してOptionsMonitorオブジェクトを作成する場合は、Optionsオブジェクトを作成および初期化するためのOptionsFactoryオブジェクトと、提供されたOptionsオブジェクトをキャッシュするためのOptionsMonitorCacheオブジェクトと、構成オプションデータの変化を検出して通知を外部に送信するためのOptionsChangeTokenSourceオブジェクトのセットが必要です.public class OptionsMonitor :IOptionsMonitor where TOptions : class, new()
{
private readonly IOptionsMonitorCache _cache;
private readonly IOptionsFactory _factory;
private readonly IEnumerable> _sources;
internal event Actionstring> _onChange;
public OptionsMonitor(IOptionsFactory factory,IEnumerable> sources,IOptionsMonitorCache 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(Actionstring> listener)
{
var disposable = new ChangeTrackerDisposable(this, listener);
_onChange += disposable.OnChange;
return disposable;
}
internal class ChangeTrackerDisposable : IDisposable
{
private readonly Actionstring> _listener;
private readonly OptionsMonitor _monitor;
public ChangeTrackerDisposable(OptionsMonitor monitor, Actionstring> listener)
{
_listener = listener;
_monitor = monitor;
}
public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);
public void Dispose() => _monitor._onChange -= OnChange;
}
}
OptionsMonitorオブジェクトが提供するOptionsオブジェクトは、常にIOptionsMonitorCacheオブジェクトが表すキャッシュコンテナに由来するため、提供された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]:構成システムとの統合
public interface IOptionsMonitorCache where TOptions : class
{
TOptions GetOrAdd(string name, Func createOptions);
bool TryAdd(string name, TOptions options);
bool TryRemove(string name);
void Clear();
}
public class OptionsCache : IOptionsMonitorCache
where TOptions : class
{
private readonly ConcurrentDictionary<string, Lazy> _cache = new ConcurrentDictionary<string, Lazy>(StringComparer.Ordinal);
public void Clear() => _cache.Clear();
public virtual TOptions GetOrAdd(string name, Func createOptions) => _cache.GetOrAdd(name, new Lazy(createOptions)).Value;
public virtual bool TryAdd(string name, TOptions options) => _cache.TryAdd(name, new Lazy(() => options));
public virtual bool TryRemove(string name) => _cache.TryRemove(name, out var ignored);
}
Optionsモデルがキャッシュを表すインタフェースをIOptionsMonitorCacheと命名したのは、キャッシュが最初にOptionsMonitorオブジェクトサービスであるためであり、このオブジェクトはOptionsオブジェクトを担持する元のデータソースの監視を実現し、データ更新が検出された後にキャッシュされたOptionsオブジェクトをタイムリーに置き換えることを目的としている.
public interface IOptionsMonitor<out TOptions>
{
TOptions CurrentValue { get; }
TOptions Get(string name);
IDisposable OnChange(Actionstring> listener);
}
IOptionsMonitorインタフェースで定義されたOnChangeメソッドを直接呼び出して新しいOptionsオブジェクトを適用するコールバックを登録するだけでなく、次のような同名の拡張メソッドを呼び出すこともできます.OnChangeメソッドで登録されているコールバックは、Actionのタイプの依頼オブジェクトであり、出力パラメータが欠けているためOptionsの名前が区別されるため、登録されているコールバックはすべてのOptionsオブジェクトに適用されます.なお,この2つのOnChangeメソッドの戻りタイプはIDisposableであり,実際にはコールバックの登録を表しており,戻りオブジェクトのDisposeメソッドを呼び出して登録を解除することができる.public static class OptionsMonitorExtensions
{
public static IDisposable OnChange( this IOptionsMonitor monitor, Action listener)
=> monitor.OnChange((o, _) => listener(o));
}
.NET Coreアプリケーションは、常にIChangeTokenオブジェクトを使用して通知を送信します.Optionsデータの変化を監視するOptionsMonitorオブジェクトも例外ではありません.IOptionsMonitorオブジェクトが、データの変化を検出した後に通知を外部に送信するために使用されるIChangeTokenオブジェクトは、IOptionsChangeTokenSourceオブジェクトによって作成されます.OptionsChangeTokenSourceインタフェースのNameプロパティはOptionsの名前を表し、前述のIChangeTokenオブジェクトはGetChangeTokenメソッドによって提供されます.public interface IOptionsChangeTokenSource<out TOptions>
{
string Name { get; }
IChangeToken GetChangeToken();
}
Optionsモデルは、OptionsMonitorインタフェースのデフォルト実装として、次のようなOptionsMonitorタイプを定義します.コンストラクション関数を呼び出してOptionsMonitorオブジェクトを作成する場合は、Optionsオブジェクトを作成および初期化するためのOptionsFactoryオブジェクトと、提供されたOptionsオブジェクトをキャッシュするためのOptionsMonitorCacheオブジェクトと、構成オプションデータの変化を検出して通知を外部に送信するためのOptionsChangeTokenSourceオブジェクトのセットが必要です.public class OptionsMonitor :IOptionsMonitor where TOptions : class, new()
{
private readonly IOptionsMonitorCache _cache;
private readonly IOptionsFactory _factory;
private readonly IEnumerable> _sources;
internal event Actionstring> _onChange;
public OptionsMonitor(IOptionsFactory factory,IEnumerable> sources,IOptionsMonitorCache 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(Actionstring> listener)
{
var disposable = new ChangeTrackerDisposable(this, listener);
_onChange += disposable.OnChange;
return disposable;
}
internal class ChangeTrackerDisposable : IDisposable
{
private readonly Actionstring> _listener;
private readonly OptionsMonitor _monitor;
public ChangeTrackerDisposable(OptionsMonitor monitor, Actionstring> listener)
{
_listener = listener;
_monitor = monitor;
}
public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);
public void Dispose() => _monitor._onChange -= OnChange;
}
}
OptionsMonitorオブジェクトが提供するOptionsオブジェクトは、常にIOptionsMonitorCacheオブジェクトが表すキャッシュコンテナに由来するため、提供された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]:構成システムとの統合