浅析C#更改令牌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); }
从这里我们可以看出ChangeTokenRegistrationOnChangeTokenFired
方法,也就是说令牌取消的时候调用的就是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来实现重复更改令牌的操作,实现无限次的触发通知。虽然博主能力和文笔都十分有限,但依然希望同学们能从中获取收获,这也是作为写作人最大的动力。