【五分鐘的dotnet】是一個利用您的碎片化時間來學習和豐富.net知識的博文系列。它所包含了.net體系中可能會涉及到的方方面面,好比C#的小細節,AspnetCore,微服務中的.net知識等等。
5min+不是超過5分鐘的意思,"+"是知識的增長。so,它是讓您花費5分鐘如下的時間來提高您的知識儲備量。git
前段時間在閱讀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
包裏面也有發現它的身影。迷惑了好久以後,今天總算能夠找個機會來扒一扒它,看看它究竟是一個什麼東西。github
其實,ChangeToken
在微軟官方的AspNet Core教程文檔中是有專門介紹它的文章:《使用 ASP.NET Core 中的更改令牌檢測更改》。可是該篇文章我我的以爲有點偏重於講使用,而對原理比較淡化。這怎麼能知足得了咱們程序員的探索欲😏,確定要再百度一波呀,而後……………… 又是隻有一篇文章,仍是出自於我們園子。(手動博客園牛逼!)編程
其實,從MSDN裏面的第一句描述以及這個類的命名,我們仍是能夠讀懂它的大體意思的。這不就是一個像觀察者模式的東西嗎? 當某某某發生變化的時候,就執行一個某某操做。安全
那麼直接用委託訂閱不行?我們先來想想使用傳統的委託來進行操做是什麼樣子的?異步
Action myAction = () => { Console.WriteLine("人來了!"); }; myAction += () => { Console.WriteLine("狗要叫!"); }; myAction += () => { Console.WriteLine("貓要叫!"); };
相似於醬紫哈。當觀察到人來了的時候,貓狗就都會叫起來。ide
可是這樣寫您會發現,其實上面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中重要接口之一,理解它的「職責」和「原理」是頗有必要的。這樣才能便於後期咱們學習它所在的「不一樣圈子」,好比文中說起到的物理文件系統等。
最後,偷偷說一句:創做不易,點個推薦吧.....