AspNetCore Options 源码解读
Option
监控原理OptionsMonitor
与OptionsManager
Option
Option
监控原理
ChangeToken.OnChange
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
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();
}
FileConfigurationProvider
1
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);
})ConfigurationRoot
1
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
注册,并发会线程安全吗??