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

系列介紹

【五分鐘的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 中的更改令牌檢測更改》。可是該篇文章我我的以爲有點偏重於講使用,而對原理比較淡化。這怎麼能知足得了咱們程序員的探索欲😏,確定要再百度一波呀,而後……………… 又是隻有一篇文章,仍是出自於我們園子。(手動博客園牛逼!編程

x

觀察者?

其實,從MSDN裏面的第一句描述以及這個類的命名,我們仍是能夠讀懂它的大體意思的。這不就是一個像觀察者模式的東西嗎? 當某某某發生變化的時候,就執行一個某某操做。安全

那麼直接用委託訂閱不行?我們先來想想使用傳統的委託來進行操做是什麼樣子的?異步

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

相似於醬紫哈。當觀察到人來了的時候,貓狗就都會叫起來。ide

可是這樣寫您會發現,其實上面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

相關文章
相關標籤/搜索