淺析C#更改令牌ChangeToken
簡單實(shí)例
要想更好的了解一個新的知識,首先知道它是做啥的,其次要知道它怎么做。所以還是老規(guī)矩,咱們先通過簡單的示例開始,這樣更方便了解。ChangeToken
本身是一個靜態(tài)類,它的核心入口OnChange
方法包含兩個參數(shù),一個是傳遞IChangeToken
接口實(shí)例來獲取令牌,另一個是令牌取消之后進(jìn)行的回調(diào)操作。博主本人知道的關(guān)于IChangeToken接口的在CLR中的實(shí)現(xiàn)類有兩個,分別是CancellationChangeToken
和CompositeChangeToken
類,接下來咱們就分別介紹一下這兩個類的簡單使用。
CancellationChangeToken示例
咱們先來演示CancellationChangeToken
類的使用方式,這也是默認(rèn)情況下可以使用ChangeToken的最簡單方式。首先定義一個TestCancellationChangeToken類來包裝一下CancellationChangeToken,實(shí)現(xiàn)如下
public class TestCancellationChangeToken { private CancellationTokenSource tokenSource; /// <summary> /// 獲取CancellationChangeToken實(shí)例方法 /// </summary> public CancellationChangeToken CreatChanageToken() { tokenSource = new CancellationTokenSource(); return new CancellationChangeToken(tokenSource.Token); } /// <summary> /// 取消CancellationTokenSource /// </summary> public void CancelToken() { tokenSource.Cancel(); } }
這個類非常簡單,包含一個CancellationTokenSource類型的屬性,一個創(chuàng)建CancellationChangeToken實(shí)例的方法和一個取消CancellationTokenSource的CancelToken方法。注意看實(shí)現(xiàn)的CreatChanageToken
方法,這個方法每次調(diào)用都需要創(chuàng)建一個新的CancellationTokenSource和CancellationChangeToken實(shí)例,創(chuàng)建CancellationChangeToken實(shí)例需要傳遞CancellationToken實(shí)例。CancelToken
方法里是調(diào)用的CancellationTokenSource的Cancel方法。接下來我們就來看一下如何使用定義的這個類
//聲明類的實(shí)例 TestCancellationChangeToken cancellationChangeToken = new TestCancellationChangeToken(); ChangeToken.OnChange(() => cancellationChangeToken.CreatChanageToken(), () => { System.Console.WriteLine($"{DateTime.Now:HH:mm:ss}被觸發(fā)可一次"); }); //模擬多次調(diào)用CancelToken for (int i = 0; i < 3; i++) { Thread.Sleep(1000); cancellationChangeToken.CancelToken(); }
上面的示例演示了通過ChangeToken類使用我們定義的TestCancellationChangeToken類,ChangeToken的OnChange
方法傳遞了創(chuàng)建新CancellationChangeToken實(shí)例的方法委托,第二個參數(shù)則是取消令牌的回調(diào)操作,這樣便可以重復(fù)的使用取消操作,為了演示效果在循環(huán)里重復(fù)調(diào)用CancelToken方法顯示的打印結(jié)果是
16:40:15被觸發(fā)可一次
16:40:16被觸發(fā)可一次
16:40:17被觸發(fā)可一次
CancellationChangeToken類是通過ChangeToken實(shí)現(xiàn)重復(fù)取消觸發(fā)調(diào)用的簡單實(shí)現(xiàn),兩者將結(jié)合的時候需要自己包裝一下,因?yàn)镃hangeToken的第一個參數(shù)需要每次獲取CancellationChangeToken實(shí)例的委托,所以需要將它包裝到工作類中。
CompositeChangeToken示例
實(shí)際開發(fā)中很多時候都需要一些關(guān)聯(lián)的場景,比如我觸發(fā)了一個取消操作,我想把和這個相關(guān)聯(lián)的其它操作也取消,也就是咱們說的有相關(guān)性。CompositeChangeToken
正是可以綁定一個相關(guān)性的IChangeToken集合,當(dāng)這個IChangeToken集合中有任何一個實(shí)例進(jìn)行取消操作的時候,當(dāng)前CompositeChangeToken實(shí)例也會執(zhí)行取消操作,咱們就大致演示一下它的使用方式,首先是定義一個使用類TestCompositeChangeToken來模擬包裝CompositeChangeToken實(shí)例
public class TestCompositeChangeToken { //聲明一個CancellationTokenSource集合 private List<CancellationTokenSource> _cancellationTokenSources; /// <summary> /// 獲取CompositeChangeToken實(shí)例方法 /// </summary> public CompositeChangeToken CreatChanageToken() { //初始化三個CancellationTokenSource實(shí)例 var firstCancellationTokenSource = new CancellationTokenSource(); var secondCancellationTokenSource = new CancellationTokenSource(); var threeCancellationTokenSource = new CancellationTokenSource(); //分別注冊一個回調(diào)操作用于演示 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實(shí)例注冊一個取消回調(diào)方便演示 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實(shí)例的CreatChanageToken
方法中創(chuàng)建了三個CancellationTokenSource實(shí)例,然后用這三個實(shí)例初始化了一個CancellationChangeToken集合,用這個集合初始化了一個CompositeChangeToken實(shí)例,來模擬集合中的CancellationChangeToken實(shí)例和CompositeChangeToken實(shí)例的相關(guān)性。CancelToken
方法中隨機(jī)獲取了一個CancellationTokenSource實(shí)例進(jìn)行取消操作,來更好的演示相關(guān)性。因?yàn)镃ompositeChangeToken類也實(shí)現(xiàn)了IChangeToken接口,所以用起來都一樣,大致如下
//聲明類的實(shí)例 TestCompositeChangeToken compositeChangeToken = new TestCompositeChangeToken(); ChangeToken.OnChange(() => compositeChangeToken.CreatChanageToken(), () => { System.Console.WriteLine($"{DateTime.Now:HH:mm:ss}被觸發(fā)可一次"); }); //模擬多次調(diào)用CancelToken for (int i = 0; i < 3; i++) { Thread.Sleep(1000); compositeChangeToken.CancelToken(); }
為了演示可以重復(fù)觸發(fā)取消操作,這里依然使用循環(huán)的方式模擬多次觸發(fā)。因?yàn)榇嬖谙嚓P(guān)性,所以打印的執(zhí)行結(jié)果如下
12:05:18被觸發(fā)可一次
compositeChangeToken被取消
secondCancellationTokenSource被取消12:05:19被觸發(fā)可一次
compositeChangeToken被取消
firstCancellationTokenSource被取消12:05:20被觸發(fā)可一次
compositeChangeToken被取消
secondCancellationTokenSource被取消
從結(jié)果上可以看到任何一個相關(guān)聯(lián)CancellationChangeToken實(shí)例的CancellationTokenSource實(shí)例被取消的話,與其相關(guān)的CompositeChangeToken實(shí)例也執(zhí)行了取消操作,在有些場景下還是比較實(shí)用的。
源碼探究
上面我們通過簡單的示例大致了解了ChangeToken是做啥的,以及它怎么使用。通過名字可以得知,它叫更改令牌,說明可以動態(tài)產(chǎn)生令牌的值。它涉及到了幾個核心的操作相關(guān)分別是IChangeToken接口、CancellationChangeToken、CompositeChangeToken和ChangeToken靜態(tài)類,通過上面咱們的示例和講解我們大致了解了這幾個類型的關(guān)系,為了方便閱讀和思維帶入咱們就按照方便理解的順序來挨個講解。
友情提示:本文設(shè)計(jì)到粘貼出來的相關(guān)源碼,這些源碼是省略掉一部分過程的。因?yàn)槲覀冎饕橇私馑膶?shí)現(xiàn),無關(guān)緊要的代碼可能會影響閱讀效果。而且這次的GitHub源碼地址我更換為https://hub.fastgit.org而沒有使用官方的https://github.com,主要是GitHub近期很不穩(wěn)定經(jīng)常打不開。fastgit是github的鏡像網(wǎng)站,展示的內(nèi)容是完全一致的,最主要的是打開很流暢。
IChangeToken接口
首先便是IChangeToken
接口,它是整個ChangeToken系列的入口操作,ChangeToken的OnChange操作也是通過它的實(shí)現(xiàn)類發(fā)起的。它的作用就是獲取一個可以更改的令牌,也就是可以重復(fù)觸發(fā)的令牌,咱們就先來看一下它的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈]
public interface IChangeToken { /// <summary> /// 用來標(biāo)識是否發(fā)生過更改 /// </summary> bool HasChanged { get; } /// <summary> /// 指示令牌是否支持回調(diào) /// </summary> bool ActiveChangeCallbacks { get; } /// <summary> /// 當(dāng)令牌取消時執(zhí)行的回調(diào) /// </summary> /// <param name="callback">回調(diào)執(zhí)行委托</param> /// <param name="state">回調(diào)委托的參數(shù)</param> /// <returns>An <see cref="IDisposable"/> that is used to unregister the callback.</returns> IDisposable RegisterChangeCallback(Action<object> callback, object state); }
它定義的接口成員非常簡單,總結(jié)起來就是兩類,一個是判斷是否發(fā)生過更改相關(guān)即取消操作,一個是發(fā)生過更改之后的回調(diào)操作。它只是定義一個標(biāo)準(zhǔn)任何實(shí)現(xiàn)這個標(biāo)準(zhǔn)的類都具備被ChangeToken的OnChange方法提供可更換令牌的能力。
CancellationChangeToken實(shí)現(xiàn)
上面我們了解了IChageToken接口定義的成員相關(guān),而CancellationChangeToken
則是IChageToken接口最常使用默認(rèn)的實(shí)現(xiàn),了解了它的實(shí)現(xiàn),我們就可以更好的知道ChangeToken的OnChange方法是如何工作的,所以這里我選擇了先講解CancellationChangeToken相關(guān)的實(shí)現(xiàn),這樣會讓接下來的閱讀變得更容易理解。好了直接看它的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈]
public class CancellationChangeToken : IChangeToken { /// <summary> /// 唯一構(gòu)造函數(shù)通過CancellationChangeToken初始化 /// </summary> public CancellationChangeToken(CancellationToken cancellationToken) { Token = cancellationToken; } /// <summary> /// 因?yàn)樗峭ㄟ^CancellationToken實(shí)現(xiàn)具備回調(diào)的能力 /// </summary> public bool ActiveChangeCallbacks { get; private set; } = true; /// <summary> /// 根據(jù)CancellationToken的IsCancellationRequested屬性判斷令牌是否已取消 /// </summary> public bool HasChanged => Token.IsCancellationRequested; /// <summary> /// 接收傳遞進(jìn)來的CancellationToken /// </summary> private CancellationToken Token { get; } /// <summary> /// 注冊回調(diào)操作 /// </summary> /// <returns></returns> public IDisposable RegisterChangeCallback(Action<object> callback, object state) { try { //本質(zhì)還是通過CancellationToken完成它回調(diào)操作的功能 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的本質(zhì)還是CancellationToken
的包裝類,因?yàn)槲覀兛吹搅薈ancellationChangeToken類的核心操作實(shí)現(xiàn)都是依賴的CancellationChangeToken類的實(shí)現(xiàn)完成的。它的HasChanged屬性
和RegisterChangeCallback方法
都是直接調(diào)用的CancellationChangeToken類的實(shí)現(xiàn)。
ChangeToken類的實(shí)現(xiàn)
上面我們講解了IChangeToken接口的相關(guān)實(shí)現(xiàn),也說明了因?yàn)镃hangeToken類是依賴IChangeToken接口實(shí)現(xiàn)來完成的,所以咱們是從IChangeToken類開始講解的。了解了上面的實(shí)現(xiàn)之后,咱們就可以直接來看ChangeToken
相關(guān)的實(shí)現(xiàn)了,而我們使用的就是它的OnChange方法[點(diǎn)擊查看源碼👈]
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方法其實(shí)是包含兩個重載的,一個是無參委托一個是有參委托。無參委托沒啥好說的,通過有參回調(diào)我們可以給回調(diào)傳遞參數(shù),這個參數(shù)是方法傳遞每次回調(diào)委托獲取的值取決于State本身的值是什么。最重要的是它們兩個都是返回了ChangeTokenRegistration<T>
類的實(shí)例,也就是說OnChange方法本身是一個外觀用來隱藏ChangeTokenRegistration
private class ChangeTokenRegistration<TState> : IDisposable { //生產(chǎn)IChangeToken實(shí)例的委托 private readonly Func<IChangeToken> _changeTokenProducer; //回調(diào)委托 private readonly Action<TState> _changeTokenConsumer; //回調(diào)參數(shù) private readonly TState _state; public ChangeTokenRegistration(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state) { _changeTokenProducer = changeTokenProducer; _changeTokenConsumer = changeTokenConsumer; _state = state; //執(zhí)行changeTokenProducer得到IChangeToken實(shí)例 IChangeToken token = changeTokenProducer(); //調(diào)用RegisterChangeTokenCallback方法傳遞IChangeToken實(shí)例 RegisterChangeTokenCallback(token); } }
通過上面我們了解到ChangeTokenRegistration
正是實(shí)現(xiàn)ChangeToken效果的核心,而它的構(gòu)造函數(shù)里通過執(zhí)行傳遞進(jìn)來產(chǎn)生IChangeToken新實(shí)例的委托得到了新的IChangeToken實(shí)例。這里需要注意,每次執(zhí)行Func<IChangeToken>
都會得到新的IChangeToken實(shí)例。然后調(diào)用了RegisterChangeTokenCallback方法,而這個方法只需要傳遞得到的IChangeToken實(shí)例即可。接下來我們只需要看RegisterChangeTokenCallback方法實(shí)現(xiàn)即可[點(diǎn)擊查看源碼👈]
private void RegisterChangeTokenCallback(IChangeToken token) { //給IChangeToken實(shí)例注冊回調(diào)操作 //回調(diào)操作正是執(zhí)行當(dāng)前ChangeTokenRegistration實(shí)例的OnChangeTokenFired方法 IDisposable registraton = token.RegisterChangeCallback(s => ((ChangeTokenRegistration<TState>)s).OnChangeTokenFired(), this); SetDisposable(registraton); }
從這里我們可以看出ChangeTokenRegistrationOnChangeTokenFired
方法,也就是說令牌取消的時候調(diào)用的就是OnChangeTokenFired方法,咱們直接看一下這個方法的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈]
private void OnChangeTokenFired() { //獲取一個新的IChangeToken實(shí)例 IChangeToken token = _changeTokenProducer(); try { //執(zhí)行注冊的回調(diào)操作 _changeTokenConsumer(_state); } finally { //又調(diào)用了RegisterChangeTokenCallback注冊當(dāng)前IChangeToken實(shí)例 RegisterChangeTokenCallback(token); } }
看上面的代碼我第一反應(yīng)就是豁然開朗,通過OnChangeTokenFired
方法的實(shí)現(xiàn)仿佛一切都透徹了。首先調(diào)用_changeTokenProducer
委托獲取新的IChangeToken實(shí)例,即我們通過即我們通過ChangeToken的OnChange方法第一個參數(shù)傳遞的委托。然后執(zhí)行_changeTokenConsumer
委托,即我們通過ChangeToken的OnChange方法第二個參數(shù)傳遞的委托。最后傳遞當(dāng)前通過_changeTokenProducer委托產(chǎn)生的新IChangeToken實(shí)例調(diào)用RegisterChangeTokenCallback方法,即咱們上面的那個方法,這樣就完成了類似一個遞歸的操作。執(zhí)行完之后又將這套流程重新注冊了一遍,然后形成了這種可以持續(xù)觸發(fā)的操作。
上面的RegisterChangeTokenCallback方法里里調(diào)用了SetDisposable
方法,這個方法主要是判斷Token有沒有被取消。因?yàn)槲覀冊谑褂肐ChangeToken實(shí)例的時候會涉及到多線程共享的問題,而IChangeToken實(shí)例本身設(shè)計(jì)考慮到了線程安全問題,我們可以大致看下SetDisposable的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈]
private IDisposable _disposable; private static readonly NoopDisposable _disposedSentinel = new NoopDisposable(); private void SetDisposable(IDisposable disposable) { //讀取_disposable實(shí)例 IDisposable current = Volatile.Read(ref _disposable); //如果當(dāng)前_disposable實(shí)例等于_disposedSentinel實(shí)例則說明當(dāng)前ChangeTokenRegistration已被釋放, //則直接釋放IChangeToken實(shí)例然后返回 if (current == _disposedSentinel) { disposable.Dispose(); return; } //線程安全交換,如果之前_disposable的值等于_disposedSentinel說明被釋放過了 //則釋放IChangeToken實(shí)例 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"); } }
因?yàn)镃hangeTokenRegistration是實(shí)現(xiàn)了IDisposable接口,所以我們可以先看下Dispose方法的實(shí)現(xiàn),這樣的話會讓大家更好的理解它的這個釋放體系[點(diǎn)擊查看源碼👈]
public void Dispose() { //因?yàn)開disposable初始值是null所以把NoopDisposable實(shí)例賦值給_disposable并調(diào)用Dispose方法 Interlocked.Exchange(ref _disposable, _disposedSentinel).Dispose(); }
因?yàn)槌跏悸暶鱛disposable變量的時候初始值是null
,這里把NoopDisposable實(shí)例賦值給_disposable并調(diào)用Dispose方法。其實(shí)Dispose方法啥也沒做就是為了標(biāo)記一下,因?yàn)镃hangeTokenRegistration類并未涉及到非托管資源相關(guān)的操作。
通過SetDisposable方法結(jié)合Dispose方法,我們可以理解在觸回調(diào)操作的時候會調(diào)SetDisposable方法進(jìn)行判斷ChangeTokenRegistration有沒有被釋放過,如果已經(jīng)被釋放則直接釋放掉傳遞的IChangToken實(shí)例。因?yàn)镃hangeToken的OnChange方法返回的就是ChangeTokenRegistration實(shí)例,如果這個被釋放則意味了OnChange傳遞的IChangeToken實(shí)例也必須要釋放。
通過上面講解了ChangeTokenRegistration<TState>
類的實(shí)現(xiàn)我們了解到了ChangeToken類工作的本質(zhì),其實(shí)非常簡單,為了怕大家沒看明白在這里咱們簡單的總結(jié)一下ChangeToken的整體工作過程。
- ChangeToken靜態(tài)類只包裝了
OnChange
方法,這個方法傳遞的核心參數(shù)是產(chǎn)生IChangeToken實(shí)例的委托和CancellationTokenSource實(shí)例取消后的回調(diào)操作。這里的IChangeToken實(shí)例和ChangeToken靜態(tài)類沒啥關(guān)系,就是名字長得像。 - ChangeToken靜態(tài)類的OnChange方法本質(zhì)是包裝一個
ChangeTokenRegistration<TState>
實(shí)例。ChangeTokenRegistration是ChangeToken類工作的核心,它的工作方式是,初始化的時候生成IChangeToken實(shí)例然后調(diào)用RegisterChangeTokenCallback方法,在RegisterChangeTokenCallback方法方法中給IChangeToken實(shí)例的RegisterChangeCallback方法注冊了回調(diào)操作。 - RegisterChangeCallback回調(diào)操作注冊一個調(diào)用OnChangeTokenFired方法的操作,通過上面的源碼我們知道RegisterChangeCallback本質(zhì)是給CancellationToken注冊回調(diào),所以當(dāng)CancellationTokenSource調(diào)用Cancel的時候回執(zhí)行OnChangeTokenFired方法。
- OnChangeTokenFired方法是核心操作,它首先是獲取一個新的IChangeToken實(shí)例,然后執(zhí)行注冊的回調(diào)操作。然后又調(diào)用了RegisterChangeTokenCallback傳遞了最新獲取的IChangeToken實(shí)例,這樣的話就形成了一個類似遞歸的操作,而這個遞歸的終止條件就是ChangeTokenRegistration
有沒有被釋放。所以才能實(shí)現(xiàn)動態(tài)更改令牌的效果。
一句話總結(jié)一下就是,RegisterChangeCallback中給CancellationChangeToken的回調(diào)注冊了調(diào)用OnChangeTokenFired方法的操作,OnChangeTokenFired方法中有調(diào)用了RegisterChangeCallback方法給它傳遞了生成的IChangeToken實(shí)例,而回調(diào)操作都是同一個,只是不斷被新的IChangeToken實(shí)例調(diào)用。
CompositeChangeToken實(shí)現(xiàn)
上面我們說過之所以最后來說CompositeChangeToken
的實(shí)現(xiàn),完全是因?yàn)樗鼘儆谠鰪?qiáng)的操作。如果大家理解了簡單的工作方式的流程,然后再去嘗試了解復(fù)雜的操作可能會更容易理解。所以咱們先說了CancellationChangeToken這個IChangeToken最簡單的實(shí)現(xiàn),然后說了ChangeToken靜態(tài)類,讓大家對整體的工作機(jī)制有了解,最后咱們再來講解CompositeChangeToken,這樣的話大家會很容易就理解這個操作方式的。咱們還是先從入口的構(gòu)造函數(shù)入手吧[點(diǎn)擊查看源碼👈]
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實(shí)例ActiveChangeCallbacks為true * 則CompositeChangeToken的ActiveChangeCallbacks也為true * 因?yàn)镃ompositeChangeToken可以關(guān)聯(lián)IChangeToken集合中的任何一個有效實(shí)例 */ if (ChangeTokens[i].ActiveChangeCallbacks) { ActiveChangeCallbacks = true; break; } } } }
從上面的構(gòu)造函數(shù)可以看出IChangeToken集合中存在可用的實(shí)例即可,因?yàn)镃ompositeChangeToken只需要知道集合中存在可用的即可,而不是要求全部的IChangeToken都可以用。通過ChangeToken靜態(tài)類的源碼我們可以知道,CancellationTokenSource的Cancel方法執(zhí)行后調(diào)用的是IChangeToken的RegisterChangeCallback方法,也就是說回調(diào)觸發(fā)的操作就是這個方法,我們來看一下這個方法的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈]
private CancellationTokenSource _cancellationTokenSource; public IDisposable RegisterChangeCallback(Action<object> callback, object state) { //核心方法 EnsureCallbacksInitialized(); //這里的CancellationTokenSource注冊CompositeChangeToken的回調(diào)操作 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注冊過回調(diào)操作,如果不是第一次則直接返回 if (_registeredCallbackProxy) { return; } //加鎖 意味著這個操作要線程安全 lock (_callbackLock) { if (_registeredCallbackProxy) { return; } //實(shí)例化CancellationTokenSource,因?yàn)镽egisterChangeCallback方法里再用 _cancellationTokenSource = new CancellationTokenSource(); _disposables = new List<IDisposable>(); //循環(huán)要關(guān)聯(lián)的IChangeToken集合 for (int i = 0; i < ChangeTokens.Count; i++) { //判斷注冊進(jìn)來的IChangeToken實(shí)例是否支持回調(diào)操作 if (ChangeTokens[i].ActiveChangeCallbacks) { //給IChangeToken實(shí)例注冊回調(diào)操作執(zhí)行_onChangeDelegate委托 IDisposable disposable = ChangeTokens[i].RegisterChangeCallback(_onChangeDelegate, this); //返回值加入IDisposable集合 _disposables.Add(disposable); } } //標(biāo)識注冊過了,防止重復(fù)注冊引發(fā)的多次觸發(fā) _registeredCallbackProxy = true; } }
上面的代碼我們看到了核心的關(guān)聯(lián)回調(diào)操作是執(zhí)行了_onChangeDelegate委托,它是被OnChange方法初始化的。這一步操作其實(shí)就是把關(guān)聯(lián)的IChangeToken實(shí)例注冊_onChangeDelegate委托操作,咱們來看下CompositeChangeToken的OnChange
方法實(shí)現(xiàn)[點(diǎn)擊查看源碼👈]
private static void OnChange(object state) { //獲取傳遞的CompositeChangeToken實(shí)例 var compositeChangeTokenState = (CompositeChangeToken)state; //判斷CancellationTokenSource是否被初始化過 if (compositeChangeTokenState._cancellationTokenSource == null) { return; } //加鎖 說明這一步是線程安全操作 lock (compositeChangeTokenState._callbackLock) { try { /** * 取消當(dāng)前實(shí)例的CancellationTokenSource * 這樣才能執(zhí)行CompositeChangeToken注冊的回調(diào)操作 */ compositeChangeTokenState._cancellationTokenSource.Cancel(); } catch { } } //獲取EnsureCallbacksInitialized方法中注冊的集合,即IChangeToken集合的回調(diào)返回值集合 List<IDisposable> disposables = compositeChangeTokenState._disposables; //不為null則通過循環(huán)的方式挨個釋放掉 Debug.Assert(disposables != null); for (int i = 0; i < disposables.Count; i++) { disposables[i].Dispose(); } }
通過上面的OnChange方法我們得知,它主要是實(shí)現(xiàn)了在注冊進(jìn)來的任意IChangeToken實(shí)例如果發(fā)生了取消操作則當(dāng)前的CompositeChangeToken實(shí)例RegisterChangeCallback進(jìn)來的回調(diào)操作也要執(zhí)行,而且這一步要釋放掉所有注冊IChangeToken實(shí)例,因?yàn)橹灰幸粋€IChangeToken實(shí)例執(zhí)行了取消操作,則CompositeChangeToken實(shí)例和其它注冊進(jìn)來相關(guān)聯(lián)的IChangeToken實(shí)例都要取消。
IChangeToken還有一個HasChange屬性來標(biāo)識當(dāng)前IChangeToken是否被取消,咱們來看下CompositeChangeToken是如何實(shí)現(xiàn)這個屬性的[點(diǎn)擊查看源碼👈]
public bool HasChanged { get { //如果當(dāng)前實(shí)例的CancellationTokenSource被取消過則說明當(dāng)前CompositeChangeToken已被取消 if (_cancellationTokenSource != null && _cancellationTokenSource.Token.IsCancellationRequested) { return true; } //循環(huán)注冊進(jìn)來的關(guān)聯(lián)的IChangeToken集合 for (int i = 0; i < ChangeTokens.Count; i++) { //如果存在關(guān)聯(lián)的IChangeToken實(shí)例有被取消的那么也認(rèn)為當(dāng)前CompositeChangeToken已被取消 if (ChangeTokens[i].HasChanged) { //調(diào)用OnChange是否關(guān)聯(lián)的IChangeToken實(shí)例 OnChange(this); return true; } } //否則則沒被取消過 return false; } }
通過上面的代碼可以看到HasChanged屬性的設(shè)計(jì)思路符合它整體的設(shè)計(jì)思路。判斷是否取消的標(biāo)識有兩個,如果當(dāng)前實(shí)例的CancellationTokenSource被取消過則說明當(dāng)前CompositeChangeToken已被取消,還有就是如果存在關(guān)聯(lián)的IChangeToken實(shí)例有被取消的那么也認(rèn)為當(dāng)前CompositeChangeToken也被取消。好了通過上面這一部分整體的源碼,我們可以總結(jié)一下CompositeChangeToken的整體實(shí)現(xiàn)思路。
- CompositeChangeToken的取消回調(diào)操作分為兩部分,一個是基于傳遞的IChangeToken集合中激活更改回調(diào)即ActiveChangeCallbacks為true的實(shí)例,另一個則是它自身維護(hù)通過RegisterChangeCallback注冊進(jìn)來的委托,這個委托是它內(nèi)部維護(hù)的CancellationTokenSource實(shí)現(xiàn)的。
- 因?yàn)镃ompositeChangeToken的RegisterChangeCallback方法中給注冊進(jìn)來的IChangeToken集合中的每一個ActiveChangeCallbacks的實(shí)例注冊了取消回調(diào)操作,所以當(dāng)ChangeToken靜態(tài)類觸發(fā)RegisterChangeCallback回調(diào)操作的時候回調(diào)用CompositeChangeToken的OnChange方法。
- CompositeChangeToken的OnChange方法中會取消CompositeChangeToken內(nèi)部維護(hù)的CancellationTokenSource,也就是觸發(fā)CompositeChangeToken類本身的回調(diào),并且釋放注冊進(jìn)來的其他相關(guān)聯(lián)的IChangeToken實(shí)例,從而實(shí)現(xiàn)了關(guān)聯(lián)取消的操作。
通過源碼探究部分,我們分別展示了關(guān)于IChangeToken接口,以及它最簡單的實(shí)現(xiàn)類CancellationChangeToken類的實(shí)現(xiàn),然后根據(jù)CancellationChangeToken類的實(shí)現(xiàn)講解了ChangeToken靜態(tài)類是如何實(shí)現(xiàn)動態(tài)令牌更改的,最后又探究了IChangeToken接口的另一個高級的可以關(guān)聯(lián)更改令牌操作的CompositeChangeToken的用法,通過這樣一個流程,博主本人認(rèn)為是更容易理解的。
自定義IChangeToken實(shí)現(xiàn)
上面我們看到了CancellationChangeToken的使用方式非常簡單,但是也存在一定的限制,那就是需要外部傳遞CancellationTokenSource的實(shí)例。其實(shí)很多時候我們只需要知道你是IChangeToken實(shí)例就好了能滿足被ChangeToken靜態(tài)類使用就好了,至于傳遞CancellationTokenSource啥的不需要外部關(guān)心,能相應(yīng)的操作就行了,比如在.Net Core的Configuration體系中的ConfigurationReloadToken,它是用來實(shí)現(xiàn)配置發(fā)生變化通知ConfigurationProvider重新加載數(shù)據(jù)完成自動刷新操作,我們來看一下它的實(shí)現(xiàn)方式[點(diǎn)擊查看源碼👈]
public class ConfigurationReloadToken : IChangeToken { //內(nèi)部定義了CancellationTokenSource實(shí)例 private CancellationTokenSource _cts = new CancellationTokenSource(); public bool ActiveChangeCallbacks => true; public bool HasChanged => _cts.IsCancellationRequested; /// <summary> /// 給當(dāng)前的CancellationTokenSource實(shí)例注冊操作 public IDisposable RegisterChangeCallback(Action<object> callback, object state) => _cts.Token.Register(callback, state); /// <summary> /// 添加OnReload方法,供外部取消使用 /// </summary> public void OnReload() => _cts.Cancel(); }
它在ConfigurationReloadToken類的內(nèi)部聲明了CancellationTokenSource類型的屬性,然后提供了可以取消CancellationTokenSource實(shí)例的方法OnReload,這樣的話邏輯可以在內(nèi)部消化,而不像在外部傳遞。當(dāng)重新獲取它的實(shí)例的時候額外提供一個可獲取ConfigurationReloadToken新實(shí)例的方法即可[點(diǎn)擊查看源碼👈]
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken(); private void RaiseChanged() { //直接交換一個新的ConfigurationReloadToken實(shí)例 ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); //取消上一個ConfigurationReloadToken實(shí)例實(shí)現(xiàn)更改通知操作 previousToken.OnReload(); }
這樣的話獲取Token的實(shí)現(xiàn)就非常簡單了,直接返回ConfigurationReloadToken的屬性即可不再需要一堆額外的操作,這樣就可以保證每次通過GetReloadToken方法獲取的IChangeToken實(shí)例都是未失效的。
public IChangeToken GetReloadToken() => _changeToken;
總結(jié)
本文我們講解了ChangeToken相關(guān)的體系,設(shè)計(jì)到了IChangeToken接口的幾個實(shí)現(xiàn)類和ChangeToken靜態(tài)類是如何實(shí)現(xiàn)通過取消令牌重復(fù)觸發(fā)的,其實(shí)本質(zhì)也就是它的名字,可以動態(tài)去更改令牌,實(shí)現(xiàn)的大致思路就是類似遞歸的操作,在回調(diào)通知里獲取新的變更令牌實(shí)例,重新注冊當(dāng)前回調(diào)操作形成遞歸。因?yàn)镮ChangeToken實(shí)例都是引用類型,而我們傳遞的CancellationTokenSource實(shí)例也是引用類型,所以我們在使用的時候沒感覺有什么變化,但其實(shí)如果你每次打印它的實(shí)例都是不一樣的,因?yàn)閮?nèi)部已經(jīng)更換了新的實(shí)例。好了我們大致總結(jié)一下
- IChangeToken接口是滿足ChangeToken靜態(tài)類的必須操作,默認(rèn)提供的CancellationChangeToken類則是IChangeToken接口最簡單的實(shí)現(xiàn),它是依賴CancellationTokenSource實(shí)現(xiàn)注冊和取消通知相關(guān)操作的。
- ChangeToken靜態(tài)類的工作依賴它的OnChange方法注冊的參數(shù),一個是獲取IChangeToken實(shí)例的委托,一個是令牌取消執(zhí)行的操作。其實(shí)現(xiàn)的本質(zhì)是在CancellationChangeToken的Register方法里注冊重新注冊的操作。也就是通過ChangeToken靜態(tài)類的OnChange方法第一個參數(shù)委托,執(zhí)行這個委托獲取新的IChangeToken實(shí)例,當(dāng)然它包含的CancellationChangeToken實(shí)例也是最新的。然后ChangeToken靜態(tài)類的OnChange方法第二個參數(shù),即回調(diào)操作重新注冊給這個新的實(shí)例,這個更改操作對外部都是無感知的,但其實(shí)內(nèi)部早已經(jīng)更換了新的實(shí)例。
- CompositeChangeToken實(shí)現(xiàn)關(guān)聯(lián)取消更改操作的本質(zhì)是,給一堆IChangeToken實(shí)例注冊相同的OnChange操作,如果有一個IChangeToken實(shí)例執(zhí)行了取消則通過OnChange方法取消當(dāng)前CompositeChangeToken實(shí)例和相關(guān)聯(lián)的IChangeToken實(shí)例,防止同一個CompositeChangeToken實(shí)例重復(fù)被觸發(fā)。
這個系列我們講解了取消令牌相關(guān),其核心都是對CancellationTokenSource的包裝,因?yàn)槟J(rèn)的CancellationTokenSource的實(shí)例默認(rèn)只能被取消一次,但是很多場景需要能多次甚至無限次觸發(fā)這種通知,比如.Net Core的Configuration體系,每次配置發(fā)生變更都需要執(zhí)行響應(yīng)的刷新操作。因此衍生出來了IChangeToken相關(guān),結(jié)合輔助的ChangeToken來實(shí)現(xiàn)重復(fù)更改令牌的操作,實(shí)現(xiàn)無限次的觸發(fā)通知。雖然博主能力和文筆都十分有限,但依然希望同學(xué)們能從中獲取收獲,這也是作為寫作人最大的動力。
到此這篇關(guān)于淺析C#更改令牌ChangeToken的文章就介紹到這了,更多相關(guān)C#更改令牌ChangeToken內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#中List轉(zhuǎn)IList的實(shí)現(xiàn)
本文主要介紹了C#中List轉(zhuǎn)IList的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07C#?網(wǎng)域賬號(Domain)驗(yàn)證的實(shí)現(xiàn)
本文主要介紹了C#?網(wǎng)域賬號(Domain)驗(yàn)證的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-04-04C#基于數(shù)據(jù)庫存儲過程的AJAX分頁實(shí)例
這篇文章主要介紹了C#基于數(shù)據(jù)庫存儲過程的AJAX分頁實(shí)現(xiàn)方法,以實(shí)例形式詳細(xì)講述了數(shù)據(jù)庫存儲過程的定義、數(shù)據(jù)庫的訪問及Ajax的實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-01-01C# paddlerocrsharp識別身份證號的實(shí)現(xiàn)示例
paddlerocrsharp可以進(jìn)行圖片識別,本文主要介紹了C# paddlerocrsharp識別身份證號的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02C#實(shí)現(xiàn)六大設(shè)計(jì)原則之接口隔離原則
這篇文章介紹了C#實(shí)現(xiàn)六大設(shè)計(jì)原則之接口隔離原則的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02講解.NET環(huán)境下繪制模糊數(shù)學(xué)中隸屬函數(shù)分布圖
講解.NET環(huán)境下繪制模糊數(shù)學(xué)中隸屬函數(shù)分布圖...2007-11-11