配置源的同步 IOptionsMonitor 使用
//以下demo演示使用IOptionsMonitor重新加載配置并當重新加載配置是執行回呼函式
var configuration = new ConfigurationBuilder().AddJsonFile(path: "profile.json",
optional: false,
reloadOnChange: true).Build();
new ServiceCollection().AddOptions().Configure<Profile>(configuration).BuildServiceProvider().GetRequiredService<IOptionsMonitor<Profile>>().OnChange(profile => Console.WriteLine($"data reload: {profile.Age}"));
Console.Read();
public class Profile
{
public int Age { get; set; }
}
配置源的同步 IOptionsMonitor 原始碼分析
當檔案變更時如何向外發送通知的以及 Reload data,
以JsonConfiguration為例:
FileConfigurationProvider通過FileProvider.Watch當檔案發生改變的時候會呼叫Load,load方法做了兩件事情,1.呼叫子類同名虛方完成具體資料的reload data(由具體實作類:JsonConfigurationProvider)2,提供呼叫OnReload(由父類ConfigurationProvider實作),完成對外發送data change的通知,OnReload內呼叫了_reloadToken.OnReload發送回呼通知并產生一個新的ConfigurationReloadToken重新賦值給_reloadToken,通知注冊到FileConfigurationProvider._reloadToken的回呼,那么想接收到檔案改變的訊息只需要通過GetReloadToken()得到_reloadToken屬性并將回呼函式注冊進去即可,
如下是此三個類的繼承關系JsonConfiguration->FileConfigurationProvider->ConfigurationProvider
知道了這些在看下ConfigurationRoot,
public abstract class FileConfigurationProvider : ConfigurationProvider, IDisposable
{
public FileConfigurationProvider(FileConfigurationSource source!!)
{
Source = source;
if (Source.ReloadOnChange && Source.FileProvider != null)
{
_changeTokenRegistration = ChangeToken.OnChange(
() => Source.FileProvider.Watch(Source.Path!),
() =>
{
// 重新從JsonFile Load 資料并
Load(reload: true);
});
}
}
private void Load(bool reload)
{
IFileInfo? file = Source.FileProvider?.GetFileInfo(Source.Path ?? string.Empty);
using Stream stream = OpenRead(file);
try
{
// 此處呼叫具體實作類的Load 方法例如JsonConfigurationProvider
Load(stream);
}
// 發送OnReload 并重新生成ConfigurationReloadToken共下次使用,
OnReload();
}
}
public class JsonConfigurationProvider : FileConfigurationProvider
{
public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }
public override void Load(Stream stream)
{
Data = https://www.cnblogs.com/hts92/p/JsonConfigurationFileParser.Parse(stream);
}
}
public abstract class ConfigurationProvider : IConfigurationProvider
{
protected void OnReload()
{
ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
previousToken.OnReload();
}
public IChangeToken GetReloadToken()
{
return _reloadToken;
}
}
ConfigurationRoot會回圈呼叫把所有的providers 并通過IConfigurationProvider.GetReloadToken()得到FileConfigurationProvider._reloadToken,然后注冊上RaiseChanged作為回呼函式,以檔案系統為例,當檔案發生改動時會呼叫此回呼函式,此回呼函式又會呼叫ConfigurationRoot的_changeToken.OnReload()向外發送通知,
ConfigurationChangeTokenSource:注冊的時機為ConfigurationChangeTokenSource.Configure.
我們作為使用者注冊的回呼事件就是注冊在OptionsMonitor._onChange中,當用戶使用OptionsMonitor時,其在構造方法通過DI拿到使用ConfigurationChangeTokenSource作為包裝類,其包裝的是ConfigurationRoot._changeToken,并把自身的事件OptionsMonitor._onChange作為回呼函式注冊在包裝類ConfigurationChangeTokenSource.包裝的ConfigurationRoot._changeToken中,自此完成了整個回呼鏈條,
// ConfigurationRoot向IConfigurationProvider注冊回呼函式拼接回呼鏈條,
public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
_providers = providers;
_changeTokenRegistrations = new List<IDisposable>(providers.Count);
foreach (IConfigurationProvider p in providers)
{
p.Load();
// 回呼鏈條拼接
_changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));
}
private void RaiseChanged()
{
ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
previousToken.OnReload();
}
}
// ConfigurationChangeTokenSource 包裝類與注冊 OptionsConfigurationServiceCollectionExtensions
public class ConfigurationChangeTokenSource<TOptions> : IOptionsChangeTokenSource<TOptions>
{
private IConfiguration _config;
public ConfigurationChangeTokenSource(IConfiguration config) : this(Options.DefaultName, config){}
public IChangeToken GetChangeToken()
{
return _config.GetReloadToken();
}
}
public static class OptionsConfigurationServiceCollectionExtensions
{
public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services!!, string? name, IConfiguration config!!, Action<BinderOptions>? configureBinder) where TOptions : class
{
services.AddOptions();
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}
}
public class OptionsMonitor<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> : IOptionsMonitor<TOptions>, IDisposable
where TOptions : class
{
internal event Action<TOptions, string>? _onChange;
public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
{
ChangeToken.OnChange(
() => source.GetChangeToken(),
(name) => InvokeChanged(name),
source.Name);
private void InvokeChanged(string? name)
{
name = name ?? Options.DefaultName;
_cache.TryRemove(name);
TOptions options = Get(name);
if (_onChange != null)
{
_onChange.Invoke(options, name);
}
}
}
public IDisposable OnChange(Action<TOptions, string> listener)
{
var disposable = new ChangeTrackerDisposable(this, listener);
_onChange += disposable.OnChange;
return disposable;
}
}
總結
整個程序回呼使用了兩個ConfigurationReloadToken分別是,1. FileConfigurationProvider提供了一個ConfigurationReloadToken 2.提供了一個ConfigurationRoot._changeToken ,回呼鏈條的拼接是,1.FileConfigurationProvider建構式中檔案的Watch與FileConfigurationProvider._reloadToken同時在這里也完成了資料的reload data 2 ConfigurationRoot的建構式中與IConfigurationProvider._reloadToken進行的回呼鏈條拼接 ,第三次拼接是把用戶注冊的回呼函注冊在OptionsMonito的event上,OptionsMonito在建構式中通過DI容器獲取到ConfigurationRoot._changeToken中包裝類,并把event作為回呼函式進行注冊.
通過以上代碼分析,當我們向創建一個具有相同通知機制的回呼鏈條并且有多次通知 需要利用CancellationToken與 ChangeToken.OnChange 進行鏈接,同時要注意每次鏈接后向下發送訊息時,要重新生成changeToken,因為changeToken的特性是只能發送一次訊息,向多次必須重新生成ChangeToken例如
ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
previousToken.OnReload();
文章中提到的代碼,請在source.dot.net快速搜索預覽
本文來自博客園,作者:一身大膘,轉載請注明原文鏈接:https://www.cnblogs.com/hts92/p/16012929.html
如果該篇文章對您有幫助的話,可以點一下右下角的【推薦】
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/446925.html
標籤:.NET Core
下一篇:(九)React Ant Design Pro + .Net5 WebApi:后端環境搭建-IdentityServer4-簡單配置
