配置的同步涉及到兩個方面:第一,對原始的配置文件實施監控併在其發生變化之後從新載入配置;第二,配置重新載入之後及時通知應用程式進而使後者能夠使用最新的配置。要瞭解配置同步機制的實現原理,先得從認識一個名為ConfigurationReloadToken的類型開始。 ...
配置的同步涉及到兩個方面:第一,對原始的配置文件實施監控併在其發生變化之後從新載入配置;第二,配置重新載入之後及時通知應用程式進而使後者能夠使用最新的配置。要瞭解配置同步機制的實現原理,先得從認識一個名為ConfigurationReloadToken的類型開始。 [ 本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、從ConfigurationReloadToken說起
二、Configuration對象與配置文件的同步
三、應用重新載入的配置
四、同步流程總結
一、從ConfigurationReloadToken說起
.NET Core絕大部分的數據同步場景下都使用到一個名為ChangeToken的對象,該對象綁定到某個需要被監控的對象,並該對象發生改變是對外發送通知,我們可以註冊在被監控數據發生改變時可以自動執行的回調。在配置同步場景中,ConfigurationProvider會利用FileProvider監控配置文件的變化,併在變化時從新載入配置。ConfigurationReloadToken就是一個通知配置已經被重新載入的ChangeToken。
ConfigurationReloadToken本質上是對一個CancellationTokenSource對象的封裝。如果我們對.NET基於Task對象的並行/非同步編程有所瞭解的話,相信對CancellationTokenSource應該不會感到模式。總的來說,我們可以利用CancellationTokenSource創建的CancellationToken向某個非同步執行的Task發送“取消任務”的信號。如下麵的代碼片段所示,ConfigurationReloadToken的HasChanged屬性對應的是這個CancellationTokenSource對象的IsCancellationRequested。通過調用RegisterChangeCallback方法註冊的回調實際上是註冊到 CancellationTokenSource創建的CancellationToken對象上。
1: public class ConfigurationReloadToken : IChangeToken
2: {
3: private CancellationTokenSource _cts = new CancellationTokenSource();
4:
5: public void OnReload()
6: {
7: _cts.Cancel();
8: }
9:
10: public IDisposable RegisterChangeCallback(Action<object> callback, object state)
11: {
12: return _cts.Token.Register(callback, state);
13: }
14:
15: public bool ActiveChangeCallbacks
16: {
17: get { return true; }
18: }
19:
20: public bool HasChanged
21: {
22: get { return _cts.IsCancellationRequested; }
23: }
24: }
當ConfigurationReloadToken的OnReload方法被執行的時候,這被封裝的CancellationTokenSource對象的Cancel方法隨之被調用。我們知道一旦這個Cancel方法被調用之後,CancellationTokenSource的IsCancellationRequested會馬上變成True,意味著ConfigurationReloadToken的HasChanged屬性也立即變成True。由於調用RegisterChangeCallback方法註冊的回調最是註冊到CancellationTokenSource創建的CancellationToken上的,所以該回調會在OnLoad方法被調用之後自動執行。
二、Configuration對象與配置文件的同步
在《聊聊預設支持的各種配置源》和《深入瞭解三種針對文件(JSON、XML與INI)的配置源》中,我們介紹了系統預定義的若幹配置源,它們都通過相應的ConfigurationSource類型來表示,對於這些ConfigurationSource來說,只有針對配置文件的FileConfigurationSource才會涉及到配置同步的問題。其實這一點也可以由它們的定義看出來,因為只有FileConfigurationSource這個抽象類才定義瞭如下這個ReloadOnChange屬性來控制當配置文件改變之後是否需要重新載入配置。換句話說,配置的同步首先需要解決的是由ConfigurationBuilder創建的Configuration對象與原始配置文件的內容同步的問題,而解決這個問題的途徑就是對配置實施監控,併在文件發生改變之後自動重新載入配置。
1: public abstract class FileConfigurationSource : IConfigurationSource
2: {
3: ...
4: public bool ReloadOnChange { get; set; }
5: }
我們知道 FileConfigurationProvdier總是利用一個FileProvider對象來讀取對應的配置文件,除了讀取文件內容之外,FileProvider的Watch方法自身就提供了文件監控的能力。FileConfigurationProvdier利用FileProvider監控配置文件,併在配置文件發生改變時自動載入配置的操作實現在如下所示的代碼片段中。
1: public abstract class FileConfigurationProvider : ConfigurationProvider
2: {
3: ...
4: public FileConfigurationProvider(FileConfigurationSource source)
5: {
6: this.Source = source;
7: if (source.ReloadOnChange && (this.Source.FileProvider != null))
8: {
9: ChangeToken.OnChange(() => source.FileProvider.Watch(source.Path), this.Load);
10: }
11: }
12: }
三、應用重新載入的配置
Configuration對象與配置文件的同步問題解決之後,還需要讓應用程式感知到使用的Configuration對象已經發生改變,並且使之能夠將新的配置應用到程式之中。從編程的角度來講,這個問題很容易解決,我們只需要調用Configuration對象的GetReloadToeken方法得到一個ChangeToken對象,並將重新應用配置的操作註冊作為回調註冊到這個ChangeToken上面就可以了。
1: public interface IConfiguration
2: {
3: ...
4: IChangeToken GetReloadToken();
5: }
程式應用重新配置的回調是註冊到Configuration對象的GetReloadToken方法返回的ChangeToken對象上,而Configuration對象的重新載入最終是通過調用所有ConfigurationProvider的Load方法來實現的,所以兩者之間必然存在著某種聯繫。說的具體一點,應用程式可以通過這個ChangeToken感知到配置系統針對ConfigurationProvider的Load方法的調用。要瞭解兩者之間的聯繫,我們必須先弄清楚Configuration的 GetReloadToken方法返回的是怎樣一個ChangeToken對象。
一個Configuration對象代表配置樹的某個節點,對於組成同一棵配置樹的所有Configuration對象來說,它們的GetReloadToken方法返回的ChangeToken都來源於代表根節點的ConfigurationRoot對象。說的更加具體一點,當我們調用它們的GetReloadToken的時候,返回的其實是調用ConfigurationRoot的同名方法的返回值,那麼我們有必要瞭解一下ConfigurationRoot的GetReloadToken方法的邏輯。
1: public class ConfigurationRoot : IConfigurationRoot
2: {
3: private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
4: private IList<IConfigurationProvider> _providers;
5:
6: public ConfigurationRoot(IList<IConfigurationProvider> providers)
7: {
8: _providers = providers;
9:
10: foreach (var provider in providers)
11: {
12: provider.Load();
13: ChangeToken.OnChange(() => provider.GetReloadToken(), this.RaiseChanged);
14: }
15: }
16:
17: public IChangeToken GetReloadToken()
18: {
19: return _changeToken;
20: }
21:
22: private void RaiseChanged()
23: {
24: Interlocked.Exchange<ConfigurationReloadToken>(ref _changeToken, new ConfigurationReloadToken()).OnReload();
25: }
26:
27: public void Reload()
28: {
29: foreach (var provider in _providers)
30: {
31: provider.Load();
32: }
33: this.RaiseChanged();
34: }
35: }
如上面的代碼片段所示,ConfigurationRoot的GetReloadToken方法返回的是通過欄位_changeToken表示的一個ConfigurationReloadToken對象。私有方法RaiseChanged通過調用ConfigurationReloadToken對象的OnReload向訂閱者發送配置重新被載入的通知,由於ChangeToken只能使用一次,所以該方法總是為_changeToken欄位附上一個新的ConfigurationReloadToken對象。
針對這個RaiseChanged方法的調用發生在兩個地方,第一個地方發生在ConfigurationRoot的Reload方法上,也就是說當我們調用該方法以手工的方式重新載入配置的時候,註冊到Configuration對象提供的ChangeToken上的回調也會自動執行。
針對RaiseChanged方法的調用還出現在ConfigurationRoot構造函數中。如上面的代碼片段所示,ConfigurationRoot會調用每個ConfigurationProvdier的GetReloadToken方法,並將針對RaiseChanged方法的調用作為回調註冊到返回的ChangeToken上,也就是說註冊到Configuration對象提供的ChangeToken上的回調實際上註冊到ConfigurationProvider提供的ChangeToken上。既然如此,如果 ConfigurationProvider提供的這個ChangeToken能夠反映針對Load方法的調用,那麼上面提到的關於Configuration提供的ChangeToken與ConfigurationProvider的Load方法之間的聯繫就建立起來了。那麼ConfigurationProvider的Load方法與ChangeToken方法返回的ChangeToken究竟有沒有關係呢?
1: public abstract class ConfigurationProvider : IConfigurationProvider
2: {
3: private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
4:
5: public IChangeToken GetReloadToken()
6: {
7: return_reloadToken;
8: }
9:
10: protected void OnReload()
11: {
12: Interlocked.Exchange<ConfigurationReloadToken>(ref_reloadToken, new ConfigurationReloadToken()).OnReload();
13: }
14: }
如上面的代碼片段所示,抽象類ConfigurationProvider的GetRealoadToken方法返回的是一個通過欄位_reloadToken表示的ConfigurationReloadToken對象。該類型還定義了一個受保護的OnReload方法,該方法具有與上面介紹的RaiseChanged方法一樣的邏輯,意味著ConfigurationProvider實際上是調用這個方法對外發送配置被重新載入的通知。針對這個OnLoad方法的調用發生在FileConfigurationProvider的Load方法中。所以上面提到的讓ConfigurationProvider提供的ChangeToken能夠反映針對Load方法的調用最終實現在FileConfigurationProvider中。
1: public abstract class FileConfigurationProvider : ConfigurationProvider
2: {
3: ...
4: public override void Load()
5: {
6: ...
7: base.OnReload();
8: }
9: }
四、同步流程總結
上面我們通過代碼分析的方式捋清了配置文件在發生改變的時候為什麼會導致配置的重新載入,註冊到Configuration通過GetRealoadToken方法提供的ChangeToken上的回調為什麼會自動執行。可能都有讀者的腦子裡面還是比較暈,所以我們利用如下所示的序列圖繼續對這個過程進行講解。用於讀取配置文件內容的FileConfigurationProvder會調用FileProvder的Watch方法來監控文件的變化(實際上真正用於文件監控的實PhysicalFileProvider所示用的FileSystemWatcher),並且通過向返回的ChangeToken註冊回調的方式來調用自身的Load方法來實現配置配置的重新載入。