前言
在上篇文章淺談C#取消令牌CancellationTokenSource一文中我們講解了CancellationTokenSource,它的主要功能就是分發一個令牌,當我取消令牌我可以進行一些回調操作或者通過令牌狀態得知被取消。在上文的結尾處我們也提到了,默認情況下CancellationTokenSource產生的Token是一次性的,Cancel操作之后就沒辦法再復用了,只能釋放掉了。而微軟也很貼心的為我們提供了一個解決方案來解決這問題,那就是我們今天要說的更改令牌ChangeToken,它看起來可以讓CancellationTokenSource產生的Token多次觸發,本文我們就來講解ChangeToken相關。
簡單示例
要想更好的了解一個新的知識,首先知道它是做啥的,其次要知道它怎么做。所以還是老規矩,咱們先通過簡單的示例開始,這樣更方便了解。ChangeToken
本身是一個靜態類,它的核心入口OnChange
方法包含兩個參數,一個是傳遞IChangeToken
接口實例來獲取令牌,另一個是令牌取消之后進行的回調操作。博主本人知道的關於IChangeToken接口的在CLR中的實現類有兩個,分別是CancellationChangeToken
和CompositeChangeToken
類,接下來咱們就分別介紹一下這兩個類的簡單使用。
CancellationChangeToken示例
咱們先來演示CancellationChangeToken
類的使用方式,這也是默認情況下可以使用ChangeToken的最簡單方式。首先定義一個TestCancellationChangeToken類來包裝一下CancellationChangeToken,實現如下
public class TestCancellationChangeToken
{
private CancellationTokenSource tokenSource;
/// <summary>
/// 獲取CancellationChangeToken實例方法
/// </summary>
public CancellationChangeToken CreatChanageToken()
{
tokenSource = new CancellationTokenSource();
return new CancellationChangeToken(tokenSource.Token);
}
/// <summary>
/// 取消CancellationTokenSource
/// </summary>
public void CancelToken()
{
tokenSource.Cancel();
}
}
這個類非常簡單,包含一個CancellationTokenSource類型的屬性,一個創建CancellationChangeToken實例的方法和一個取消CancellationTokenSource的CancelToken方法。注意看實現的CreatChanageToken
方法,這個方法每次調用都需要創建一個新的CancellationTokenSource和CancellationChangeToken實例,創建CancellationChangeToken實例需要傳遞CancellationToken實例。CancelToken
方法里是調用的CancellationTokenSource的Cancel方法。接下來我們就來看一下如何使用定義的這個類
//聲明類的實例
TestCancellationChangeToken cancellationChangeToken = new TestCancellationChangeToken();
ChangeToken.OnChange(() => cancellationChangeToken.CreatChanageToken(), () =>
{
System.Console.WriteLine($"{DateTime.Now:HH:mm:ss}被觸發可一次");
});
//模擬多次調用CancelToken
for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
cancellationChangeToken.CancelToken();
}
上面的示例演示了通過ChangeToken類使用我們定義的TestCancellationChangeToken類,ChangeToken的OnChange
方法傳遞了創建新CancellationChangeToken實例的方法委托,第二個參數則是取消令牌的回調操作,這樣便可以重復的使用取消操作,為了演示效果在循環里重復調用CancelToken方法顯示的打印結果是
16:40:15被觸發可一次
16:40:16被觸發可一次
16:40:17被觸發可一次
CancellationChangeToken類是通過ChangeToken實現重復取消觸發調用的簡單實現,兩者將結合的時候需要自己包裝一下,因為ChangeToken的第一個參數需要每次獲取CancellationChangeToken實例的委托,所以需要將它包裝到工作類中。
CompositeChangeToken示例
實際開發中很多時候都需要一些關聯的場景,比如我觸發了一個取消操作,我想把和這個相關聯的其它操作也取消,也就是咱們說的有相關性。CompositeChangeToken
正是可以綁定一個相關性的IChangeToken集合,當這個IChangeToken集合中有任何一個實例進行取消操作的時候,當前CompositeChangeToken實例也會執行取消操作,咱們就大致演示一下它的使用方式,首先是定義一個使用類TestCompositeChangeToken來模擬包裝CompositeChangeToken實例
public class TestCompositeChangeToken
{
//聲明一個CancellationTokenSource集合
private List<CancellationTokenSource> _cancellationTokenSources;
/// <summary>
/// 獲取CompositeChangeToken實例方法
/// </summary>
public CompositeChangeToken CreatChanageToken()
{
//初始化三個CancellationTokenSource實例
var firstCancellationTokenSource = new CancellationTokenSource();
var secondCancellationTokenSource = new CancellationTokenSource();
var threeCancellationTokenSource = new CancellationTokenSource();
//分別注冊一個回調操作用於演示
firstCancellationTokenSource.Token.Register(() => System.Console.WriteLine("firstCancellationTokenSource被取消"));
secondCancellationTokenSource.Token.Register(() => System.Console.WriteLine("secondCancellationTokenSource被取消"));
threeCancellationTokenSource.Token.Register(() => System.Console.WriteLine("threeCancellationTokenSource被取消"));
//加入到集合還
_cancellationTokenSources = new List<CancellationTokenSource>
{
firstCancellationTokenSource,
secondCancellationTokenSource,
threeCancellationTokenSource
};
//生成CancellationChangeToken集合
var cancellationChangeTokens = _cancellationTokenSources.Select(i => new CancellationChangeToken(i.Token)).ToList();
//傳遞給CompositeChangeToken
var compositeChangeToken = new CompositeChangeToken(cancellationChangeTokens);
//給CompositeChangeToken實例注冊一個取消回調方便演示
compositeChangeToken.RegisterChangeCallback(state => System.Console.WriteLine("compositeChangeToken被取消"),null);
return compositeChangeToken;
}
/// <summary>
/// 取消CancellationTokenSource
/// </summary>
public void CancelToken()
{
//方便演示效果在_cancellationTokenSources集合隨便獲取一個取消
_cancellationTokenSources[new Random().Next(_cancellationTokenSources.Count)].Cancel();
}
}
這里我定義了一個類,在獲取CompositeChangeToken實例的CreatChanageToken
方法中創建了三個CancellationTokenSource實例,然后用這三個實例初始化了一個CancellationChangeToken集合,用這個集合初始化了一個CompositeChangeToken實例,來模擬集合中的CancellationChangeToken實例和CompositeChangeToken實例的相關性。CancelToken
方法中隨機獲取了一個CancellationTokenSource實例進行取消操作,來更好的演示相關性。因為CompositeChangeToken類也實現了IChangeToken接口,所以用起來都一樣,大致如下
//聲明類的實例
TestCompositeChangeToken compositeChangeToken = new TestCompositeChangeToken();
ChangeToken.OnChange(() => compositeChangeToken.CreatChanageToken(), () =>
{
System.Console.WriteLine($"{DateTime.Now:HH:mm:ss}被觸發可一次");
});
//模擬多次調用CancelToken
for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
compositeChangeToken.CancelToken();
}
為了演示可以重復觸發取消操作,這里依然使用循環的方式模擬多次觸發。因為存在相關性,所以打印的執行結果如下
12:05:18被觸發可一次
compositeChangeToken被取消
secondCancellationTokenSource被取消
12:05:19被觸發可一次
compositeChangeToken被取消
firstCancellationTokenSource被取消
12:05:20被觸發可一次
compositeChangeToken被取消
secondCancellationTokenSource被取消
從結果上可以看到任何一個相關聯CancellationChangeToken實例的CancellationTokenSource實例被取消的話,與其相關的CompositeChangeToken實例也執行了取消操作,在有些場景下還是比較實用的。
源碼探究
上面我們通過簡單的示例大致了解了ChangeToken是做啥的,以及它怎么使用。通過名字可以得知,它叫更改令牌,說明可以動態產生令牌的值。它涉及到了幾個核心的操作相關分別是IChangeToken接口、CancellationChangeToken、CompositeChangeToken和ChangeToken靜態類,通過上面咱們的示例和講解我們大致了解了這幾個類型的關系,為了方便閱讀和思維帶入咱們就按照方便理解的順序來挨個講解。
友情提示:本文設計到粘貼出來的相關源碼,這些源碼是省略掉一部分過程的。因為我們主要是了解它的實現,無關緊要的代碼可能會影響閱讀效果。而且這次的GitHub源碼地址我更換為https://hub.fastgit.org而沒有使用官方的https://github.com,主要是GitHub近期很不穩定經常打不開。fastgit是github的鏡像網站,展示的內容是完全一致的,最主要的是打開很流暢。
IChangeToken接口
首先便是IChangeToken
接口,它是整個ChangeToken系列的入口操作,ChangeToken的OnChange操作也是通過它的實現類發起的。它的作用就是獲取一個可以更改的令牌,也就是可以重復觸發的令牌,咱們就先來看一下它的實現[點擊查看源碼👈]
public interface IChangeToken
{
/// <summary>
/// 用來標識是否發生過更改
/// </summary>
bool HasChanged { get; }
/// <summary>
/// 指示令牌是否支持回調
/// </summary>
bool ActiveChangeCallbacks { get; }
/// <summary>
/// 當令牌取消時執行的回調
/// </summary>
/// <param name="callback">回調執行委托</param>
/// <param name="state">回調委托的參數</param>
/// <returns>An <see cref="IDisposable"/> that is used to unregister the callback.</returns>
IDisposable RegisterChangeCallback(Action<object> callback, object state);
}
它定義的接口成員非常簡單,總結起來就是兩類,一個是判斷是否發生過更改相關即取消操作,一個是發生過更改之后的回調操作。它只是定義一個標准任何實現這個標准的類都具備被ChangeToken的OnChange方法提供可更換令牌的能力。
CancellationChangeToken實現
上面我們了解了IChageToken接口定義的成員相關,而CancellationChangeToken
則是IChageToken接口最常使用默認的實現,了解了它的實現,我們就可以更好的知道ChangeToken的OnChange方法是如何工作的,所以這里我選擇了先講解CancellationChangeToken相關的實現,這樣會讓接下來的閱讀變得更容易理解。好了直接看它的實現[點擊查看源碼👈]
public class CancellationChangeToken : IChangeToken
{
/// <summary>
/// 唯一構造函數通過CancellationChangeToken初始化
/// </summary>
public CancellationChangeToken(CancellationToken cancellationToken)
{
Token = cancellationToken;
}
/// <summary>
/// 因為它是通過CancellationToken實現具備回調的能力
/// </summary>
public bool ActiveChangeCallbacks { get; private set; } = true;
/// <summary>
/// 根據CancellationToken的IsCancellationRequested屬性判斷令牌是否已取消
/// </summary>
public bool HasChanged => Token.IsCancellationRequested;
/// <summary>
/// 接收傳遞進來的CancellationToken
/// </summary>
private CancellationToken Token { get; }
/// <summary>
/// 注冊回調操作
/// </summary>
/// <returns></returns>
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{
try
{
//本質還是通過CancellationToken完成它回調操作的功能
return Token.UnsafeRegister(callback, state);
}
catch (ObjectDisposedException)
{
ActiveChangeCallbacks = false;
}
return NullDisposable.Instance;
}
private class NullDisposable : IDisposable
{
public static readonly NullDisposable Instance = new NullDisposable();
public void Dispose()
{
}
}
}
通過上面的代碼我們可以得知,CancellationChangeToken的本質還是CancellationToken
的包裝類,因為我們看到了CancellationChangeToken類的核心操作實現都是依賴的CancellationChangeToken類的實現完成的。它的HasChanged屬性
和RegisterChangeCallback方法
都是直接調用的CancellationChangeToken類的實現。
ChangeToken類的實現
上面我們講解了IChangeToken接口的相關實現,也說明了因為ChangeToken類是依賴IChangeToken接口實現來完成的,所以咱們是從IChangeToken類開始講解的。了解了上面的實現之后,咱們就可以直接來看ChangeToken
相關的實現了,而我們使用的就是它的OnChange方法[點擊查看源碼👈]
public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
{
return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
}
public static IDisposable OnChange<TState>(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state)
{
return new ChangeTokenRegistration<TState>(changeTokenProducer, changeTokenConsumer, state);
}
它的OnChange方法其實是包含兩個重載的,一個是無參委托一個是有參委托。無參委托沒啥好說的,通過有參回調我們可以給回調傳遞參數,這個參數是方法傳遞每次回調委托獲取的值取決於State本身的值是什么。最重要的是它們兩個都是返回了ChangeTokenRegistration<T>
類的實例,也就是說OnChange方法本身是一個外觀用來隱藏ChangeTokenRegistration
private class ChangeTokenRegistration<TState> : IDisposable
{
//生產IChangeToken實例的委托
private readonly Func<IChangeToken> _changeTokenProducer;
//回調委托
private readonly Action<TState> _changeTokenConsumer;
//回調參數
private readonly TState _state;
public ChangeTokenRegistration(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state)
{
_changeTokenProducer = changeTokenProducer;
_changeTokenConsumer = changeTokenConsumer;
_state = state;
//執行changeTokenProducer得到IChangeToken實例
IChangeToken token = changeTokenProducer();
//調用RegisterChangeTokenCallback方法傳遞IChangeToken實例
RegisterChangeTokenCallback(token);
}
}
通過上面我們了解到ChangeTokenRegistration
正是實現ChangeToken效果的核心,而它的構造函數里通過執行傳遞進來產生IChangeToken新實例的委托得到了新的IChangeToken實例。這里需要注意,每次執行Func<IChangeToken>
都會得到新的IChangeToken實例。然后調用了RegisterChangeTokenCallback方法,而這個方法只需要傳遞得到的IChangeToken實例即可。接下來我們只需要看RegisterChangeTokenCallback方法實現即可[點擊查看源碼👈]
private void RegisterChangeTokenCallback(IChangeToken token)
{
//給IChangeToken實例注冊回調操作
//回調操作正是執行當前ChangeTokenRegistration實例的OnChangeTokenFired方法
IDisposable registraton = token.RegisterChangeCallback(s => ((ChangeTokenRegistration<TState>)s).OnChangeTokenFired(), this);
SetDisposable(registraton);
}
從這里我們可以看出ChangeTokenRegistration
OnChangeTokenFired
方法,也就是說令牌取消的時候調用的就是OnChangeTokenFired方法,咱們直接看一下這個方法的實現[
點擊查看源碼👈]
private void OnChangeTokenFired()
{
//獲取一個新的IChangeToken實例
IChangeToken token = _changeTokenProducer();
try
{
//執行注冊的回調操作
_changeTokenConsumer(_state);
}
finally
{
//又調用了RegisterChangeTokenCallback注冊當前IChangeToken實例
RegisterChangeTokenCallback(token);
}
}
看上面的代碼我第一反應就是豁然開朗,通過OnChangeTokenFired
方法的實現仿佛一切都透徹了。首先調用_changeTokenProducer
委托獲取新的IChangeToken實例,即我們通過即我們通過ChangeToken的OnChange方法第一個參數傳遞的委托。然后執行_changeTokenConsumer
委托,即我們通過ChangeToken的OnChange方法第二個參數傳遞的委托。最后傳遞當前通過_changeTokenProducer委托產生的新IChangeToken實例調用RegisterChangeTokenCallback方法,即咱們上面的那個方法,這樣就完成了類似一個遞歸的操作。執行完之后又將這套流程重新注冊了一遍,然后形成了這種可以持續觸發的操作。
上面的RegisterChangeTokenCallback方法里里調用了SetDisposable
方法,這個方法主要是判斷Token有沒有被取消。因為我們在使用IChangeToken實例的時候會涉及到多線程共享的問題,而IChangeToken實例本身設計考慮到了線程安全問題,我們可以大致看下SetDisposable的實現[點擊查看源碼👈]
private IDisposable _disposable;
private static readonly NoopDisposable _disposedSentinel = new NoopDisposable();
private void SetDisposable(IDisposable disposable)
{
//讀取_disposable實例
IDisposable current = Volatile.Read(ref _disposable);
//如果當前_disposable實例等於_disposedSentinel實例則說明當前ChangeTokenRegistration已被釋放,
//則直接釋放IChangeToken實例然后返回
if (current == _disposedSentinel)
{
disposable.Dispose();
return;
}
//線程安全交換,如果之前_disposable的值等於_disposedSentinel說明被釋放過了
//則釋放IChangeToken實例
IDisposable previous = Interlocked.CompareExchange(ref _disposable, disposable, current);
if (previous == _disposedSentinel)
{
disposable.Dispose();
}
//說明沒有被釋放過
else if (previous == current)
{
}
//說明別的線程操作了dispose則直接異常
else
{
throw new InvalidOperationException("Somebody else set the _disposable field");
}
}
因為ChangeTokenRegistration是實現了IDisposable接口,所以我們可以先看下Dispose方法的實現,這樣的話會讓大家更好的理解它的這個釋放體系[點擊查看源碼👈]
public void Dispose()
{
//因為_disposable初始值是null所以把NoopDisposable實例賦值給_disposable並調用Dispose方法
Interlocked.Exchange(ref _disposable, _disposedSentinel).Dispose();
}
因為初始聲明_disposable變量的時候初始值是null
,這里把NoopDisposable實例賦值給_disposable並調用Dispose方法。其實Dispose方法啥也沒做就是為了標記一下,因為ChangeTokenRegistration類並未涉及到非托管資源相關的操作。
通過SetDisposable方法結合Dispose方法,我們可以理解在觸回調操作的時候會調SetDisposable方法進行判斷ChangeTokenRegistration有沒有被釋放過,如果已經被釋放則直接釋放掉傳遞的IChangToken實例。因為ChangeToken的OnChange方法返回的就是ChangeTokenRegistration實例,如果這個被釋放則意味了OnChange傳遞的IChangeToken實例也必須要釋放。
通過上面講解了ChangeTokenRegistration<TState>
類的實現我們了解到了ChangeToken類工作的本質,其實非常簡單,為了怕大家沒看明白在這里咱們簡單的總結一下ChangeToken的整體工作過程。
- ChangeToken靜態類只包裝了
OnChange
方法,這個方法傳遞的核心參數是產生IChangeToken實例的委托和CancellationTokenSource實例取消后的回調操作。這里的IChangeToken實例和ChangeToken靜態類沒啥關系,就是名字長得像。 - ChangeToken靜態類的OnChange方法本質是包裝一個
ChangeTokenRegistration<TState>
實例。ChangeTokenRegistration是ChangeToken類工作的核心,它的工作方式是,初始化的時候生成IChangeToken實例然后調用RegisterChangeTokenCallback方法,在RegisterChangeTokenCallback方法方法中給IChangeToken實例的RegisterChangeCallback方法注冊了回調操作。 - RegisterChangeCallback回調操作注冊一個調用OnChangeTokenFired方法的操作,通過上面的源碼我們知道RegisterChangeCallback本質是給CancellationToken注冊回調,所以當CancellationTokenSource調用Cancel的時候回執行OnChangeTokenFired方法。
- OnChangeTokenFired方法是核心操作,它首先是獲取一個新的IChangeToken實例,然后執行注冊的回調操作。然后又調用了RegisterChangeTokenCallback傳遞了最新獲取的IChangeToken實例,這樣的話就形成了一個類似遞歸的操作,而這個遞歸的終止條件就是ChangeTokenRegistration
有沒有被釋放。所以才能實現動態更改令牌的效果。
一句話總結一下就是,RegisterChangeCallback中給CancellationChangeToken的回調注冊了調用OnChangeTokenFired方法的操作,OnChangeTokenFired方法中有調用了RegisterChangeCallback方法給它傳遞了生成的IChangeToken實例,而回調操作都是同一個,只是不斷被新的IChangeToken實例調用。
CompositeChangeToken實現
上面我們說過之所以最后來說CompositeChangeToken
的實現,完全是因為它屬於增強的操作。如果大家理解了簡單的工作方式的流程,然后再去嘗試了解復雜的操作可能會更容易理解。所以咱們先說了CancellationChangeToken這個IChangeToken最簡單的實現,然后說了ChangeToken靜態類,讓大家對整體的工作機制有了解,最后咱們再來講解CompositeChangeToken,這樣的話大家會很容易就理解這個操作方式的。咱們還是先從入口的構造函數入手吧[點擊查看源碼👈]
public class CompositeChangeToken : IChangeToken
{
public IReadOnlyList<IChangeToken> ChangeTokens { get; }
public bool ActiveChangeCallbacks { get; }
public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens)
{
ChangeTokens = changeTokens ?? throw new ArgumentNullException(nameof(changeTokens));
//遍歷傳入的IChangeToken集合
for (int i = 0; i < ChangeTokens.Count; i++)
{
/**
* 如果集合中存在任何一個IChangeToken實例ActiveChangeCallbacks為true
* 則CompositeChangeToken的ActiveChangeCallbacks也為true
* 因為CompositeChangeToken可以關聯IChangeToken集合中的任何一個有效實例
*/
if (ChangeTokens[i].ActiveChangeCallbacks)
{
ActiveChangeCallbacks = true;
break;
}
}
}
}
從上面的構造函數可以看出IChangeToken集合中存在可用的實例即可,因為CompositeChangeToken只需要知道集合中存在可用的即可,而不是要求全部的IChangeToken都可以用。通過ChangeToken靜態類的源碼我們可以知道,CancellationTokenSource的Cancel方法執行后調用的是IChangeToken的RegisterChangeCallback方法,也就是說回調觸發的操作就是這個方法,我們來看一下這個方法的實現[點擊查看源碼👈]
private CancellationTokenSource _cancellationTokenSource;
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{
//核心方法
EnsureCallbacksInitialized();
//這里的CancellationTokenSource注冊CompositeChangeToken的回調操作
return _cancellationTokenSource.Token.Register(callback, state);
}
private static readonly Action<object> _onChangeDelegate = OnChange;
private bool _registeredCallbackProxy;
private List<IDisposable> _disposables;
private readonly object _callbackLock = new object();
private void EnsureCallbacksInitialized()
{
//判斷是否已使用RegisterChangeCallback注冊過回調操作,如果不是第一次則直接返回
if (_registeredCallbackProxy)
{
return;
}
//加鎖 意味着這個操作要線程安全
lock (_callbackLock)
{
if (_registeredCallbackProxy)
{
return;
}
//實例化CancellationTokenSource,因為RegisterChangeCallback方法里再用
_cancellationTokenSource = new CancellationTokenSource();
_disposables = new List<IDisposable>();
//循環要關聯的IChangeToken集合
for (int i = 0; i < ChangeTokens.Count; i++)
{
//判斷注冊進來的IChangeToken實例是否支持回調操作
if (ChangeTokens[i].ActiveChangeCallbacks)
{
//給IChangeToken實例注冊回調操作執行_onChangeDelegate委托
IDisposable disposable = ChangeTokens[i].RegisterChangeCallback(_onChangeDelegate, this);
//返回值加入IDisposable集合
_disposables.Add(disposable);
}
}
//標識注冊過了,防止重復注冊引發的多次觸發
_registeredCallbackProxy = true;
}
}
上面的代碼我們看到了核心的關聯回調操作是執行了_onChangeDelegate委托,它是被OnChange方法初始化的。這一步操作其實就是把關聯的IChangeToken實例注冊_onChangeDelegate委托操作,咱們來看下CompositeChangeToken的OnChange
方法實現[點擊查看源碼👈]
private static void OnChange(object state)
{
//獲取傳遞的CompositeChangeToken實例
var compositeChangeTokenState = (CompositeChangeToken)state;
//判斷CancellationTokenSource是否被初始化過
if (compositeChangeTokenState._cancellationTokenSource == null)
{
return;
}
//加鎖 說明這一步是線程安全操作
lock (compositeChangeTokenState._callbackLock)
{
try
{
/**
* 取消當前實例的CancellationTokenSource
* 這樣才能執行CompositeChangeToken注冊的回調操作
*/
compositeChangeTokenState._cancellationTokenSource.Cancel();
}
catch
{
}
}
//獲取EnsureCallbacksInitialized方法中注冊的集合,即IChangeToken集合的回調返回值集合
List<IDisposable> disposables = compositeChangeTokenState._disposables;
//不為null則通過循環的方式挨個釋放掉
Debug.Assert(disposables != null);
for (int i = 0; i < disposables.Count; i++)
{
disposables[i].Dispose();
}
}
通過上面的OnChange方法我們得知,它主要是實現了在注冊進來的任意IChangeToken實例如果發生了取消操作則當前的CompositeChangeToken實例RegisterChangeCallback進來的回調操作也要執行,而且這一步要釋放掉所有注冊IChangeToken實例,因為只要有一個IChangeToken實例執行了取消操作,則CompositeChangeToken實例和其它注冊進來相關聯的IChangeToken實例都要取消。
IChangeToken還有一個HasChange屬性來標識當前IChangeToken是否被取消,咱們來看下CompositeChangeToken是如何實現這個屬性的[點擊查看源碼👈]
public bool HasChanged
{
get
{
//如果當前實例的CancellationTokenSource被取消過則說明當前CompositeChangeToken已被取消
if (_cancellationTokenSource != null && _cancellationTokenSource.Token.IsCancellationRequested)
{
return true;
}
//循環注冊進來的關聯的IChangeToken集合
for (int i = 0; i < ChangeTokens.Count; i++)
{
//如果存在關聯的IChangeToken實例有被取消的那么也認為當前CompositeChangeToken已被取消
if (ChangeTokens[i].HasChanged)
{
//調用OnChange是否關聯的IChangeToken實例
OnChange(this);
return true;
}
}
//否則則沒被取消過
return false;
}
}
通過上面的代碼可以看到HasChanged屬性的設計思路符合它整體的設計思路。判斷是否取消的標識有兩個,如果當前實例的CancellationTokenSource被取消過則說明當前CompositeChangeToken已被取消,還有就是如果存在關聯的IChangeToken實例有被取消的那么也認為當前CompositeChangeToken也被取消。好了通過上面這一部分整體的源碼,我們可以總結一下CompositeChangeToken的整體實現思路。
- CompositeChangeToken的取消回調操作分為兩部分,一個是基於傳遞的IChangeToken集合中激活更改回調即ActiveChangeCallbacks為true的實例,另一個則是它自身維護通過RegisterChangeCallback注冊進來的委托,這個委托是它內部維護的CancellationTokenSource實現的。
- 因為CompositeChangeToken的RegisterChangeCallback方法中給注冊進來的IChangeToken集合中的每一個ActiveChangeCallbacks的實例注冊了取消回調操作,所以當ChangeToken靜態類觸發RegisterChangeCallback回調操作的時候回調用CompositeChangeToken的OnChange方法。
- CompositeChangeToken的OnChange方法中會取消CompositeChangeToken內部維護的CancellationTokenSource,也就是觸發CompositeChangeToken類本身的回調,並且釋放注冊進來的其他相關聯的IChangeToken實例,從而實現了關聯取消的操作。
通過源碼探究部分,我們分別展示了關於IChangeToken接口,以及它最簡單的實現類CancellationChangeToken類的實現,然后根據CancellationChangeToken類的實現講解了ChangeToken靜態類是如何實現動態令牌更改的,最后又探究了IChangeToken接口的另一個高級的可以關聯更改令牌操作的CompositeChangeToken的用法,通過這樣一個流程,博主本人認為是更容易理解的。
自定義IChangeToken實現
上面我們看到了CancellationChangeToken的使用方式非常簡單,但是也存在一定的限制,那就是需要外部傳遞CancellationTokenSource的實例。其實很多時候我們只需要知道你是IChangeToken實例就好了能滿足被ChangeToken靜態類使用就好了,至於傳遞CancellationTokenSource啥的不需要外部關心,能相應的操作就行了,比如在.Net Core的Configuration體系中的ConfigurationReloadToken,它是用來實現配置發生變化通知ConfigurationProvider重新加載數據完成自動刷新操作,我們來看一下它的實現方式[點擊查看源碼👈]
public class ConfigurationReloadToken : IChangeToken
{
//內部定義了CancellationTokenSource實例
private CancellationTokenSource _cts = new CancellationTokenSource();
public bool ActiveChangeCallbacks => true;
public bool HasChanged => _cts.IsCancellationRequested;
/// <summary>
/// 給當前的CancellationTokenSource實例注冊操作
public IDisposable RegisterChangeCallback(Action<object> callback, object state) => _cts.Token.Register(callback, state);
/// <summary>
/// 添加OnReload方法,供外部取消使用
/// </summary>
public void OnReload() => _cts.Cancel();
}
它在ConfigurationReloadToken類的內部聲明了CancellationTokenSource類型的屬性,然后提供了可以取消CancellationTokenSource實例的方法OnReload,這樣的話邏輯可以在內部消化,而不像在外部傳遞。當重新獲取它的實例的時候額外提供一個可獲取ConfigurationReloadToken新實例的方法即可[點擊查看源碼👈]
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
private void RaiseChanged()
{
//直接交換一個新的ConfigurationReloadToken實例
ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
//取消上一個ConfigurationReloadToken實例實現更改通知操作
previousToken.OnReload();
}
這樣的話獲取Token的實現就非常簡單了,直接返回ConfigurationReloadToken的屬性即可不再需要一堆額外的操作,這樣就可以保證每次通過GetReloadToken方法獲取的IChangeToken實例都是未失效的。
public IChangeToken GetReloadToken() => _changeToken;
總結
本文我們講解了ChangeToken相關的體系,設計到了IChangeToken接口的幾個實現類和ChangeToken靜態類是如何實現通過取消令牌重復觸發的,其實本質也就是它的名字,可以動態去更改令牌,實現的大致思路就是類似遞歸的操作,在回調通知里獲取新的變更令牌實例,重新注冊當前回調操作形成遞歸。因為IChangeToken實例都是引用類型,而我們傳遞的CancellationTokenSource實例也是引用類型,所以我們在使用的時候沒感覺有什么變化,但其實如果你每次打印它的實例都是不一樣的,因為內部已經更換了新的實例。好了我們大致總結一下
- IChangeToken接口是滿足ChangeToken靜態類的必須操作,默認提供的CancellationChangeToken類則是IChangeToken接口最簡單的實現,它是依賴CancellationTokenSource實現注冊和取消通知相關操作的。
- ChangeToken靜態類的工作依賴它的OnChange方法注冊的參數,一個是獲取IChangeToken實例的委托,一個是令牌取消執行的操作。其實現的本質是在CancellationChangeToken的Register方法里注冊重新注冊的操作。也就是通過ChangeToken靜態類的OnChange方法第一個參數委托,執行這個委托獲取新的IChangeToken實例,當然它包含的CancellationChangeToken實例也是最新的。然后ChangeToken靜態類的OnChange方法第二個參數,即回調操作重新注冊給這個新的實例,這個更改操作對外部都是無感知的,但其實內部早已經更換了新的實例。
- CompositeChangeToken實現關聯取消更改操作的本質是,給一堆IChangeToken實例注冊相同的OnChange操作,如果有一個IChangeToken實例執行了取消則通過OnChange方法取消當前CompositeChangeToken實例和相關聯的IChangeToken實例,防止同一個CompositeChangeToken實例重復被觸發。
這個系列我們講解了取消令牌相關,其核心都是對CancellationTokenSource的包裝,因為默認的CancellationTokenSource的實例默認只能被取消一次,但是很多場景需要能多次甚至無限次觸發這種通知,比如.Net Core的Configuration體系,每次配置發生變更都需要執行響應的刷新操作。因此衍生出來了IChangeToken相關,結合輔助的ChangeToken來實現重復更改令牌的操作,實現無限次的觸發通知。雖然博主能力和文筆都十分有限,但依然希望同學們能從中獲取收獲,這也是作為寫作人最大的動力。
