【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 源代码。
总结
本期其实主要给大家介绍了IChangeToken
和ChangeToken
,关于IChangeToken
其实后期的文章中咱们也有涉及到。它其实也是.net core中重要接口之一,理解它的“职责”和“原理”是很有必要的。这样才能便于后期我们学习它所在的“不同圈子”,比如文中提及到的物理文件系统等。
最后,偷偷说一句:创作不易,点个推荐吧.....