5min+ 一个令牌走天下!.Net Core中的ChangeToken

Posted lonelyxmas

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5min+ 一个令牌走天下!.Net Core中的ChangeToken相关的知识,希望对你有一定的参考价值。

原文:【5min+】 一个令牌走天下!.Net Core中的ChangeToken

系列介绍

【五分钟的dotnet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net知识等等。
5min+不是超过5分钟的意思,"+"是知识的增加。so,它是让您花费5分钟以下的时间来提升您的知识储备量。

正文

前段时间在阅读AspNet Core的源代码中,发现了一个叫做ChangeToken的静态类。它的使用大概是这个样子:

复制代码
public ActionDescriptorCollectionProvider(
            IEnumerable<IActionDescriptorProvider> actionDescriptorProviders,
            IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders)
{
    _actionDescriptorProviders = actionDescriptorProviders
        .OrderBy(p => p.Order)
        .ToArray();

    _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();

    //here!!
    ChangeToken.OnChange(
        GetCompositeChangeToken,
        UpdateCollection);
}  

回想起来,这个东西我好像已经不止看到它一次两次了,在Microsoft.Extensions.FileProviders包里面也有发现它的身影。迷惑了很久之后,今天总算可以找个机会来扒一扒它,看看它到底是一个什么东西。

其实,ChangeToken在微软官方的AspNet Core教程文档中是有专门介绍它的文章:《使用 ASP.NET Core 中的更改令牌检测更改》。但是该篇文章我个人觉得有点偏重于讲使用,而对原理比较淡化。这怎么能满足得了我们程序员的探索欲??,肯定要再百度一波呀,然后……………… 又是只有一篇文章,还是出自于咱们园子。(手动博客园牛逼!

技术图片

观察者?

其实,从MSDN里面的第一句描述以及这个类的命名,咱们还是可以读懂它的大致意思的。这不就是一个像观察者模式的东西吗? 当某某某发生变化的时候,就执行一个某某操作。

那么直接用委托订阅不行?咱们先来想一想使用传统的委托来进行操作是什么样子的?

复制代码
Action myAction = () => { Console.WriteLine("人来了!"); };
myAction += () => { Console.WriteLine("狗要叫!"); };
myAction += () => { Console.WriteLine("猫要叫!"); };

类似于酱紫哈。当观察到人来了的时候,猫狗就都会叫起来。

但是这样写您会发现,其实上面demo中的三个事物(人、猫、狗)关联十分的密切。换成代码来理解的话,可能后期咱们会建立三个类,而他们之间的交互都是直接引用来实现的。如果类型较多,简直会演变为一个噩梦。

那么有没有好的办法呢? 那肯定是有的呀。

公认即合理?

我一直觉得所有的代码都能用咱们身边的小事来解释。所以,我又来讲故事了??。

先来回忆一下30年前,咱们人与人之间是怎么联系的。额…………好像确实很难联系上。因为当时交通和通讯工具都不发达,人们要交流只能通过见面。所以,当我想告诉某件事情给某人的时候,我必须亲自跑到他的家里,直到见到他本人或者与他的家人才能够完成。当然,还有一个好一点的办法就是托另外的一个人带个口信过去,但是这也必须要求我得见到这个中间人,还要信得过他。

在那个“通讯基本靠吼; 交通基本靠走; 治安基本靠狗”的年代,声音大好像还是有好处嘛。

技术图片

那么现在我们怎么联系呢? 我默默的从兜里摸出了波导手机(波导手机,手机中的战斗机,哦也)。这个社会,谁还没有一个手机呀,就算没有手机说不定也有电话手表。??

OK,回到上面的问题。您有没有一点灵感。 当一个类完成某个操作之后,下一个类就需要做出反应。刚开始,咱们可能是直接在A类里面显式的调用B类(只能亲自跑到他家去)。后来,我们可以选择一个委托(找一个中间人带口信,或者邮递员等)。而现在,我们可以选一个“手机”来实现了。

那么这个“手机”在代码里面是一个什么呢? 所有需要保持联系的人都得拥有它,只要“手机”在线就能进行通讯,而且所有人都拿着“手机”大家都不会觉得很奇怪? CancellationTokenSource。像不像它,您是否在项目的大部分类里面都引入了它,并且没有感到任何一点的奇怪。

所以,当大家都认可这种类似于TokenSource的东西之后,就觉得很正常,虽然咱们每次使用 CancellationTokenSource都要引入System.Threading命名空间。

CancellationTokenSource

来看看使用CancellationTokenSource来触发一个观察动作:

复制代码
var cancellationTokenSource = new CancellationTokenSource();

cancellationTokenSource.Token.Register(() =>
{
    Console.WriteLine($"{nameof(cancellationTokenSource)} 改变,触发了回调");
});

cancellationTokenSource.Cancel();

是不是很简单。咱们只需要在需要的类里面引入CancellationTokenSource就可以注册自己的回调方法,当它取消的时候就会执行响应的操作。加上CancellationTokenSource本身的线程安全,所以它从一提出来就被广泛的应用于异步编程。

可能到这里您会问,这个和咱们今天提到的ChangeToken有半毛钱关系吗? 别急,咱们慢慢来细看一下今天的主角:

复制代码
public static class ChangeToken
{
    public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer);
}

该类仅仅提供了一个OnChange的静态方法,而该方法需要一个返回类型为IChangeToken的参数。而一看这个命名**Token,是不是很像咱们上面的CancellationToken,也就是说它可能就是一个咱们公认的类似于“手机”一样的东西,拥有了它,就会得到通知。 是的,就是这个样子,这种东西官方的名称其实叫做“令牌”。所以,您可能都会猜到,它可能会具有一个注册回调的方法:

复制代码
public interface IChangeToken
{
    bool HasChanged { get; }
    bool ActiveChangeCallbacks { get; }

    IDisposable RegisterChangeCallback(Action<object> callback, object state);
}

看起来好像很符合咱们的猜想嘛。那么,它存在的意义是什么呢? 高层的抽象! 就好像我们刚才所说的“手机”,手机是抽象的概念,而“OPPO手机”、“华为手机”、还有我的“波导手机”都是它的具体实现。我们在不同的圈子可能会使用不同的手机。

比如下方的代码:

复制代码
Console.WriteLine("开始监测文件夹 c:\\temp");

var phyFileProvider = new PhysicalFileProvider("c:\\temp");
IChangeToken changeToken = phyFileProvider.Watch("*.*");
changeToken.RegisterChangeCallback(_ =>
{
    Console.WriteLine("检测到文件夹有变化!" + _);
}, "xiaoming");

Console.ReadLine();

code引用自:jackletter的博客

像不像一个叫做PhysicalFileProvider的运营商,给我发了一个“手机”(令牌)。当我拥有这个令牌之后,运营商就可以联系到我了,当它联系我的时候,我就可以做出对应的反应。比如上面是打印一排字出来。

而在“物理文件”这个圈子里面,IChangeToken的真身叫做PollingFileChangeToken;在“配置系统”这个圈子里面,IChangeToken的真身叫做ConfigurationReloadToken

如果咱们想实现自己的IChangeToken怎么办呢?还记得最上面的CancellationTokenSource吗?既然.Net为咱们提供了一个线程安全而又直接可以拿来用的工具,那我们就不用客气了:

复制代码
public class MyOwnChangeToken : IChangeToken
{
    public CancellationTokenSource _cts = new CancellationTokenSource();

    public bool ActiveChangeCallbacks => true;

    public bool HasChanged => _cts.IsCancellationRequested;

    public IDisposable RegisterChangeCallback(Action<object> callback, object state) => _cts.Token.Register(callback, state);

    public void MyOwnChange() => _cts.Cancel();
}

在“我自己的这个圈子”,就可以使用MyOwnChangeToken了,当外界获取到我的IChangeToken,我就可以触发MyOwnChange来通知他们了。

其实.NET Core中大部分的IChangeToken内部都使用了CancellationTokenSource

搞懂了IChangeToken我们就很轻松就能理解了ChangeToken,作为静态类的它,肯定是作为一个工具类的实现。

说白了我们直接使用静态方法就可以完成订阅了:

复制代码
 ChangeToken.OnChange(
    () => physicalFileProvider.Watch("*.*"),
    () => Console.WriteLine("检测到文件夹有变化!")
);

那么您可能会说,我直接使用上文那个RegisterChangeCallback方法订阅不行吗?他们有什么区别吗? 答案是:“调用次数”。使用RegisterChangeCallback的方法,只会执行一次回调内容,因为当“令牌”用了一次之后,其实它就失效了。所以上面那个监控文件改动的代码,当第二次文件改动的时候,它其实是不会再执行回调的。

而使用ChangeToken这个静态类,它就可以帮助您不断的去获取新“令牌”然后注册对应的回调,所以就能够保证咱们多次改变也能触发回调了。

所以来看上面的这一段代码 ChangeToken.OnChange(() => physicalFileProvider.Watch("*.*"),...),“phyFileProvider”这个“供应商”可以为我们提供“令牌”,当该令牌发生改动的时候,我们就有机会去完成操作了。() => physicalFileProvider.Watch("*.*")这部分代码我们可以称它为“令牌生产过程”,而() => Console.WriteLine("检测到文件夹有变化!")叫做“令牌的消费过程”。ChangeToken 干的事情就是:当消费者消费之后,就又会去让“生产过程”再生成一个令牌出来,并且在该令牌上挂载“消费过程”,这样就能保证能够一直“观察”下去了。

其实ChangeToken的实现很简单,有关它的源代码您可以参考:Github 源代码

总结

本期其实主要给大家介绍了IChangeTokenChangeToken,关于IChangeToken其实后期的文章中咱们也有涉及到。它其实也是.net core中重要接口之一,理解它的“职责”和“原理”是很有必要的。这样才能便于后期我们学习它所在的“不同圈子”,比如文中提及到的物理文件系统等。

最后,偷偷说一句:创作不易,点个推荐吧.....

技术图片

以上是关于5min+ 一个令牌走天下!.Net Core中的ChangeToken的主要内容,如果未能解决你的问题,请参考以下文章

如何从 .Net core 2.0 中的 HttpContext 获取访问令牌

从 ASP.NET Core 中的不同 HTTP 标头读取 JWT 令牌

如何从 .NET Core 6.0 中的 JWT 安全令牌中读取自定义声明值

使用 .NET Core 中的公共安全密钥配置 JWT Bearer 令牌验证

.Net Core 3.1 API 中的刷新令牌 Azure 广告身份验证

带有 ASP.NET Core WebAPI 的 Dart 中的令牌刷新并发问题