多線程筆記-CancellationToken(取消令牌)


介紹

在使用C#異步的場景,多多少少會接觸到CancellationTokenSource。它和取消異步任務相關的,CancellationToken就是它生產出來的。

任務取消執行回調

var tokenSource = new CancellationTokenSource();
tokenSource.Token.Register(() => { Console.WriteLine("線程被取消"); });

延時取消

對長時間阻塞調用的異步取消令牌應用,在某些場景中,我們需要請求外部的第三方資源,但是由於網絡等原因,可能會造成長時間的等待以致業務超時退出,這種情況可以使用 CancellationToken 來進行優化,但請求超過指定時長后退出,而不必針對每個 HttpClient 進行單獨的超時設置

        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);
        }

關聯取消

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

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 都會被取消

判斷取消

上面我們使用的方式,都是通過回調的方式得知CancellationTokenSource被取消了,沒辦法通過標識去得知CancellationTokenSource是否可用。微軟為我們提供了IsCancellationRequested屬性去判斷

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;
//打印被取消
cancellationToken.Register(() => System.Console.WriteLine("被取消了."));
//模擬傳遞的場景
Task.Run(async ()=> {
    while (!cancellationToken.IsCancellationRequested)
    {
        System.Console.WriteLine("一直在執行...");
        await Task.Delay(1000);
    }
});
//5s之后取消
tokenSource.CancelAfter(5000);

還有另一種方式,也可以主動判斷任務是否被取消,不過這種方式簡單粗暴,直接是拋出了異常。

while (true)
{
    //如果操作被取消則直接拋出異常
    cancellationToken.ThrowIfCancellationRequested();
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM