浅析C#更改令牌ChangeToken

这篇文章主要介绍了C#更改令牌ChangeToken,文中运用大量代码讲解的非常详细,感兴趣的小伙伴一起来看看这篇文章吧

简单实例

要想更好的了解一个新的知识,首先知道它是做啥的,其次要知道它怎么做。所以还是老规矩,咱们先通过简单的示例开始,这样更方便了解。ChangeToken本身是一个静态类,它的核心入口OnChange方法包含两个参数,一个是传递IChangeToken接口实例来获取令牌,另一个是令牌取消之后进行的回调操作。博主本人知道的关于IChangeToken接口的在CLR中的实现类有两个,分别是CancellationChangeTokenCompositeChangeToken类,接下来咱们就分别介绍一下这两个类的简单使用。

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这个类的具体信息,因为ChangeTokenRegistration只需要在ChangeToken内部使用。那我们就直接看一下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的RegisterChangeTokenCallback方法本质还是使用了IChangeToken实例的RegisterChangeCallback方法来实现的,不过这里的回调执行的是当前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来实现重复更改令牌的操作,实现无限次的触发通知。虽然博主能力和文笔都十分有限,但依然希望同学们能从中获取收获,这也是作为写作人最大的动力。

到此这篇关于浅析C#更改令牌ChangeToken的文章就介绍到这了,更多相关C#更改令牌ChangeToken内容请搜索得得之家以前的文章希望大家以后多多支持得得之家!

本文标题为:浅析C#更改令牌ChangeToken

基础教程推荐