【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 中的更改令牌检测更改》。但是该篇文章我个人觉得有点偏重于讲使用,而对原理比较淡化。这怎么能满足得了我们程序员的探索欲😏,肯定要再百度一波呀,然后……………… 又是只有一篇文章,还是出自于咱们园子。(手动博客园牛逼!

x

观察者?

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

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

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

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

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

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

公认即合理?

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

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

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

x

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

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中重要接口之一,理解它的“职责”和“原理”是很有必要的。这样才能便于后期我们学习它所在的“不同圈子”,比如文中提及到的物理文件系统等。

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

x

posted @ 2020-03-17 12:34  句幽  阅读(5275)  评论(7编辑  收藏  举报