AspNetCore Options 源码解读
Option监控原理OptionsMonitor与OptionsManager
Option
Option 监控原理
ChangeToken.OnChange1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40public static class ChangeToken
{
/// <summary>
/// Registers the <paramref name="changeTokenConsumer"/> action to be called whenever the token produced changes.
/// </summary>
/// <param name="changeTokenProducer">Produces the change token.</param>
/// <param name="changeTokenConsumer">Action called when the token changes.</param>
/// <returns></returns>
public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
{
if (changeTokenProducer == null)
{
throw new ArgumentNullException(nameof(changeTokenProducer));
}
if (changeTokenConsumer == null)
{
throw new ArgumentNullException(nameof(changeTokenConsumer));
}
Action<object> callback = null;
callback = (s) =>
{
// The order here is important. We need to take the token and then apply our changes BEFORE
// registering. This prevents us from possible having two change updates to process concurrently.
//
// If the token changes after we take the token, then we'll process the update immediately upon
// registering the callback.
var t = changeTokenProducer();
try
{
changeTokenConsumer();
}
finally // We always want to ensure the callback is registered
{
t.RegisterChangeCallback(callback, null);
}
};
return changeTokenProducer().RegisterChangeCallback(callback, null);
}以下每个类都会有几个相同属性或方法:
_reloadToken : ConfigurationReloadToken属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class ConfigurationReloadToken : IChangeToken
{
private CancellationTokenSource _cts = new CancellationTokenSource();
/// <summary>
/// Indicates if this token will proactively raise callbacks. Callbacks are still guaranteed to be invoked, eventually.
/// </summary>
public bool ActiveChangeCallbacks => true;
/// <summary>
/// Gets a value that indicates if a change has occurred.
/// </summary>
public bool HasChanged => _cts.IsCancellationRequested;
/// <summary>
/// Registers for a callback that will be invoked when the entry has changed. <see cref="Microsoft.Extensions.Primitives.IChangeToken.HasChanged"/>
/// MUST be set before the callback is invoked.
/// </summary>
/// <param name="callback">The callback to invoke.</param>
/// <param name="state">State to be passed into the callback.</param>
/// <returns></returns>
public IDisposable RegisterChangeCallback(Action<object> callback, object state) => _cts.Token.Register(callback, state);
/// <summary>
/// Used to trigger the change token when a reload occurs.
/// </summary>
public void OnReload() => _cts.Cancel();
}GetReloadToken() : IChangeToken方法1
2
3
4
5public IChangeToken GetReloadToken()
{
//永远返回最新的 _reloadToken ,只要调用 OnReload() 就会更新
return _reloadToken;
}OnReload() : void方法1
2
3
4
5
6
7
8protected void OnReload()
{
//生成新的ConfigurationReloadToken,赋值到 _reloadToken,并将原来的 _reloadToken 赋值给 previousToken
var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
//调用原来的 ConfigurationReloadToken 中 OnReload,会触发已经注册过回调函数
previousToken.OnReload();
}
FileConfigurationProvider1
2
3
4
5
6ChangeToken.OnChange(
() => Source.FileProvider.Watch(Source.Path),
() => {
Thread.Sleep(Source.ReloadDelay);
Load(reload: true);
});FileConfigurationProvider初始化时会在ChangeToken的changeTokenProducer传一个委托() => Source.FileProvider.Watch(Source.Path),这个委托会中会实现一个FileSystemWatcher类,这个监控类有OnChanged/OnCreated/OnDeleted/OnError/OnRenamed这些事件。e.g.
OnChanged事件,在文件修改的时候触发,其中CancelToken(<matchtoken>)是执行取消的。取消同时就会触发已经注册的回调,回调做两件事(看ChangeToken.Onchange中的callback):其一,执行changeTokenConsumer委托。其二,获取最新的reloadToken,给它注册上取消回调 。这里取消的回调就是:1
2
3
4() => {
Thread.Sleep(Source.ReloadDelay);
Load(reload: true);
})ConfigurationRoot1
2
3
4
5foreach (var p in providers)
{
p.Load();
ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged());
}ConfigurationRoot初始化的时候,会给所有ConfigurationProvider的reloadToken注册一个callback,这个callback做两件事:其一, 执行changeTokenConsumer委托。其二,获取最新的reloadToken,给它注册上取消回调 。这里
changeTokenConsumer委托是() => RaiseChanged(),changeTokenProducer委托是() => p.GetReloadToken()在
FileConfigurationProvider这一步时,文件更改最终会消费其changeTokenConsumer,其中有个方法Load(reload: true),此方法会调用FileConfigurationProvider中的OnReload()方法。前面我们说过,
OnReload()方法会生成一个新的reloadToken,同时调用之前的reloadToken的OnReload,从而触发已经注册过回调函数。FileConfigurationProvider的取消回调函数哪里来的??ConfigurationRoot的初始化构造函数会注入changeTokenConsumer:() => RaiseChanged(), 这个委托会调用OnReload,从而激活下游。
以上是整个监控链的构造过程,修改文件是否修改Options还有一个重要因素 reloadOnChange,此属性来自 ConfigurationSource。
1 | var configuration = new ConfigurationBuilder() |
OptionsMonitor 与 OptionsManager
构造函数中通过以下代码,于上游的 ConfigurationRoot 建立联系。
1 | ChangeToken.OnChange<string>( |
changeTokenProducer : () => source.GetChangeToken() ,获取到的是 IConfiguration 的 IChangeToken。换句话说,IConfiguration 上的取消回调函数来自于这里的 changeTokenConsumer 委托:(name) => InvokeChanged(name)
OptionsManager<TOptions> 是以 Singleton 生命周期注入的,每次实例化都会 new 一个 OptionsCache
OptionsManager<TOptions> 的取值是通过 Get(name) 方法, Get 是通过 cache 的 GetorAdd(name,()=>_factory.Create(name)。
_factory 即 OptionsFactory, Create 方法的实现是获取所有 IConfigureOptions<TOptions> 和 IPostConfigureOptions<TOptions> ,执行其中的 Configure 方法 , 从而执行所有委托
1 | public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) |
cache 的 GetorAdd 封装了一个支持并发的字典 ConcurrentDictionary<string, Lazy<TOptions>> ,最后是将要执行的委托 ()=>_factory.Create(name) , GetOrAdd 到这个字典中去。 Lazy 保证了多线程,并发 GetOrAdd 同一个 Key ,虽然可能会已经有多个相同委托产生,但延迟执行最后只取第一个执行的委托的返回值 TOptions 。
与 OptionsManager 相比 ,OptionsMonitor 更加灵活,首先 OptionsMonitor 下的 _cache 是由生命周期 Singleton 注入的 OptionsCache ,相较于 OptionsManager 自己实例化的私有变量 _cache,它可以随时随地清除缓存,移除新增等。
可以通过 OptionsMonitor.OnChange(Action<TOptions, string> listener) 注册事件,OptionsMonitor 有一个事件类型的字段 _onChange , 在 IConfigurationRoot reload 的时候触发事件。
总之, 虽然两个都是全局的缓存。但是 OptionsMonitor 更加灵活,可注册事件,并且随着绑定的 IConfigurationRoot 实例的变化而改变,并且触发事件,还可以管理缓存。
OptionsMonitor 是全局 Singleton ,通过 event 注册,并发会线程安全吗??