Asp.Net Core 輕鬆學-多線程之取消令牌

来源:https://www.cnblogs.com/viter/archive/2018/12/27/10184223.html
-Advertisement-
Play Games

取消令牌(CancellationToken) 是 .Net Core 中的一項重要功能,正確併合理的使用 CancellationToken 可以讓業務達到簡化代碼、提升服務性能的效果;當在業務開發中,需要對一些特定的應用場景進行深度干預的時候,CancellationToken 將發揮非常重要的... ...


前言

    取消令牌(CancellationToken) 是 .Net Core 中的一項重要功能,正確併合理的使用 CancellationToken 可以讓業務達到簡化代碼、提升服務性能的效果;當在業務開發中,需要對一些特定的應用場景進行深度干預的時候,CancellationToken 將發揮非常重要的作用。

1. 多線程請求合併數據源

在一個很常見的業務場景中,比如當請求一個文章詳細信息的時候,需要同時載入部分點贊用戶和評論內容,這裡一共有 3 個任務,如果按照常規的先請求文章信息,然後再執行請求點贊和評論,那麼我們需要逐一的按順序去資料庫中執行 3 次查詢;但是利用 CancellationToken ,我們可以對這 3 個請求同時執行,然後在所有數據源都請求完成的時候,將這些數據進行合併,然後輸出到客戶端

1.1 合併請求文章信息
       public static void Test()
        {
            Random rand = new Random();
            CancellationTokenSource cts = new CancellationTokenSource();
            List<Task<Article>> tasks = new List<Task<Article>>();
            TaskFactory factory = new TaskFactory(cts.Token);
            foreach (var t in new string[] { "Article", "Post", "Love" })
            {
                Console.WriteLine("開始請求");
                tasks.Add(factory.StartNew(() =>
                            {
                                var article = new Article { Type = t };
                                if (t == "Article")
                                {
                                    article.Data.Add("文章已載入");
                                }
                                else
                                {
                                    for (int i = 1; i < 5; i++)
                                    {
                                        Thread.Sleep(rand.Next(1000, 2000));
                                        Console.WriteLine("load:{0}", t);
                                        article.Data.Add($"{t}_{i}");
                                    }
                                }
                                return article;
                            }, cts.Token));
            }

            Console.WriteLine("開始合併結果");
            foreach (var task in tasks)
            {
                Console.WriteLine();
                var result = task.Result;
                foreach (var d in result.Data)
                {
                    Console.WriteLine("{0}:{1}", result.Type, d);
                }
                task.Dispose();
            }

            cts.Cancel();
            cts.Dispose();
            Console.WriteLine("\nIsCancellationRequested:{0}", cts.IsCancellationRequested);
        }

上面的代碼定義了一個 Test() 方法,在方法內部,首先定義了一個 CancellationTokenSource 對象,該退出令牌源內部創建了一個取消令牌屬性 Token ;接下來,使用 TaskFacory 任務工廠創建了 3 個並行任務,並把這個任務存入 List<Task> 列表對象中,在任務開始後,馬上迭代 tasks 列表,通過同步獲取每個任務的執行 Result 結果,在取消令牌沒有收到取消通知的時候,任務將正常的執行下去,在所有任務都執行完成後,將 3 個請求結果輸出到控制臺中,同時銷毀任務釋放線程資源;最後,執行 cts.Cancel()取消令牌並釋放資源,最後一句代碼將輸出令牌的狀態。

1.2 執行程式,輸出結果

通過上面的輸出介面,可以看出,紅色部分是模擬請求,這個請求時多線程進行的,Post 和 Love 交替出現,是因為在程式中通過線程休眠的方式模擬網路阻塞過程,藍色為合併結果部分,可以看到,雖然“文章信息”已經載入完成,但是因為 Post 和 Love 還在請求中,由於取消令牌未收到退出通知,所以合併結果會等待信號,在所有線程都執行完成後,通過 cts.Cancel() 通知令牌取消,所有事件執行完成,控制台列印結果黃色部分為令牌狀態,顯示為 True ,令牌已取消。

2. 對長時間阻塞調用的非同步取消令牌應用

在某些場景中,我們需要請求外部的第三方資源,比如請求天氣預報信息;但是,由於網路等原因,可能會造成長時間的等待以致業務超時退出,這種情況可以使用 CancellationToken 來進行優化,但請求超過指定時長後退出,而不必針對每個 HttpClient 進行單獨的超時設置

2.1 獲取天氣預報
        public async static Task GetToday()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            cts.CancelAfter(3000);
            HttpClient client = new HttpClient();
            var res = await client.GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts.Token);
            var result = await res.Content.ReadAsStringAsync();
            Console.WriteLine(result);

            cts.Dispose();
            client.Dispose();
        }

在上面的代碼中,首先定義了一個 CancellationTokenSource 對象,然後馬上發起了一個 HttpClient 的 GetAsync 請求(註意,這種使用 HttpClient 的方式是不正確的,詳見我的博客 HttpClient的演進和避坑 ;在 GetAsync 請求中傳入了一個取消令牌,然後立即發起了退出請求 Console.WriteLine(result); 不管 3 秒後請求是否返回,都將取消令牌等待信號,最後輸出結果釋放資源

  • 註意:如果是因為取消令牌退出引起請求中斷,將會拋出任務取消的異常 TaskCanceledException
  • 執行程式輸出結果

3. CancellationToken 的鏈式反應

可以使用創建一組令牌,通過鏈接各個令牌,使其建立通知關聯,當 CancellationToken 鏈中的某個令牌收到取消通知的時候,由鏈式中創建出來的 CancellationToken 令牌也將同時取消

3.1 創建鏈式測試代碼
public async static Task Test()
        {
            CancellationTokenSource cts1 = new CancellationTokenSource();
            CancellationTokenSource cts2 = new CancellationTokenSource();
            var cts3 = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);

            cts1.Token.Register(() =>
            {
                Console.WriteLine("cts1 Canceling");
            });
            cts2.Token.Register(() =>
            {
                Console.WriteLine("cts2 Canceling");
            });
            cts2.CancelAfter(1000);

            cts3.Token.Register(() =>
                        {
                            Console.WriteLine("root Canceling");
                        });

            var res = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts1.Token);
            var result = await res.Content.ReadAsStringAsync();
            Console.WriteLine("cts1:{0}", result);

            var res2 = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts2.Token);
            var result2 = await res2.Content.ReadAsStringAsync();
            Console.WriteLine("cts2:{0}", result2);

            var res3 = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts3.Token);
            var result3 = await res2.Content.ReadAsStringAsync();
            Console.WriteLine("cts3:{0}", result3);
        }

上面的代碼定義了 3 個 CancellationTokenSource ,分別是 cts1,cts2,cts3,每個 CancellationTokenSource 分別註冊了 Register 取消回調委托,然後,使用 HttpClient 發起 3 組網路請求;其中,設置 cts2 在請求開始 1秒 後退出,預期結果為:當 cts2 退出後,由於 cts3 是使用 CreateLinkedTokenSource(cts1.Token, cts2.Token) 創建出來的,所以 cts3 應該也會被取消,實際上,無論 cts1/cts2 哪個令牌取消,cts3 都會被取消

3.2 執行程式,輸出結果

從上圖可以看到,紅色部分輸出結果是:首先 cts2 取消,接著產生了鏈式反應導致 cts3 也跟著取消,藍色部分為 cts1 的正常請求結果,最後輸出了任務退出的異常信息

4. CancellationToken 令牌取消的三種方式

CancellationToken 定義了三種不同的取消方法,分別是 Cancel(),CancelAfter(),Dispose();這三種方式都代表了不同的行為方式

4.1 演示取消動作
        public static void Test()
        {
            CancellationTokenSource cts1 = new CancellationTokenSource();
            cts1.Token.Register(() =>
            {
                Console.WriteLine("\ncts1 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
            });
            cts1.Cancel();
            Console.WriteLine("cts1 State:{0}", cts1.IsCancellationRequested);

            CancellationTokenSource cts2 = new CancellationTokenSource();
            cts2.Token.Register(() =>
            {
                Console.WriteLine("\ncts2 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
            });
            cts2.CancelAfter(500);
            System.Threading.Thread.Sleep(1000);
            Console.WriteLine("cts2 State:{0}", cts2.IsCancellationRequested);

            CancellationTokenSource cts3 = new CancellationTokenSource();
            cts3.Token.Register(() =>
            {
                Console.WriteLine("\ncts3 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
            });
            cts3.Dispose();
            Console.WriteLine("\ncts3 State:{0}", cts3.IsCancellationRequested);
        }
4.2 執行程式,輸出結果如下

  上面的代碼定義了 3 個 CancellationTokenSource,分別是 cts1/cts2/cts3;分別執行了 3 中不同的取消令牌的方式,併在取消回調委托中輸出線程ID,從輸出介面中看出,當程式執行 cts1.Cancel() 方法後,取消令牌立即執行了回調委托,並輸出線程ID為:1;cts2.CancelAfter(500) 表示 500ms 後取消,為了獲得令牌狀態,這裡使線程休眠了 1000ms,而 cts3 則直接調用了 Dispose() 方法,從輸出結果看出,cts1 運行在和 Main 方法在同一個線程上,線程 ID 都為 1,而 cts2 由於使用了延遲取消,導致其在內部新創建了一個線程,其線程 ID 為 4;最後,cts3由於直接調用了 Dispose() 方法,但是其 IsCancellationRequested 的值為 False,表示未取消,而輸出結果也表明,沒有執行回調委托

結束語

  • 通過本文,我們學習到瞭如何在不同的應用場景下使用 CancellationToken
  • 掌握了合併請求、中斷請求、鏈式反應 三種使用方式
  • 最後還瞭解到三種不同的取消令牌方式,知道了各種不同取消方式的區別

示例代碼下載

https://files.cnblogs.com/files/viter/Ron.ThreadingDemo.zip


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 0x01 漏洞介紹 Drupal是一個開源內容管理系統(CMS),全球超過100萬個網站(包括政府,電子零售,企業組織,金融機構等)使用。兩周前,Drupal安全團隊披露了一個非常關鍵的漏洞,編號CVE-2018-7600 Drupal對錶單請求內容未做嚴格過濾,因此,這使得攻擊者可能將惡意註入表單 ...
  • 多線程 進程:一個獨立執行的程式;正在運行的程式。 線程:一個進程中有多個執行單元同時運行,一個執行單元即一個線程。 Java提供兩種多線程實現方式:1)繼承java.lang包下的Thread類,覆寫Thread類的run()方法,在run()方法中實現運行線上程上的代碼; 局限性:由於Java中 ...
  • C#操作SQL資料庫 Connection(連接)對象 形式2.”server=;Intergrated Security=true/SSPI” Command(命令)對象 形式2.new SqlCommand(Sql語句, 連接對象)//省略2,5 形式2.執行select語句(count,sum ...
  • C# 學習總結 C#-簡介(一) C#-hello world(二) C#-基本語法(三) C#-運算符(四) C#-判斷語句(五) C#-迴圈語句(六) C#-封裝(七) C#-方法(八) C#-類(九) C#-結構體(十) C#-繼承(十一) C#-多態(十二) C#-枚舉(十三) C#-異常處 ...
  • 在代碼中正確配置了log4net後,IIS上仍然不能寫日誌的情況下,只需在寫日誌的目錄添加 IIS_IUSRS 用戶,並賦與讀寫許可權即可。 ...
  • 來自:http://www.cnblogs.com/emanlee/p/3587571.html ...
  • 相信很多學習和開發wpf項目的同學都瞭解過mvvm模式,同樣,在mvvm模式下會有一個不可忽視的問題,就是怎麼在xaml中彈出窗體,而不破壞MVVM本身的結構。 關於彈出窗體的方式還是很多的,本文先講一下用觸發器做處理。 我們先要在xaml中引用 xmlns:i="http://schemas.mi ...
  • 生成解決方案時報“error CS0006: Metadata file '.../.../.../xxx.dll'could not be found"。 錯誤列表-下拉框選擇整個解決方案-錯誤,發現代碼裡面有兩個錯誤,改完錯誤再生成就好了。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...