C# CancellationToken和CancellationTokenSource的用法詳解
CancellationToken
CancellationToken有一個(gè)構(gòu)造函數(shù),可以傳入一個(gè)bool類型表示當(dāng)前的CancellationToken是否是取消狀態(tài)。另外,因?yàn)镃ancellationToken是一個(gè)結(jié)構(gòu)體,所以它還有一個(gè)空參數(shù)的構(gòu)造函數(shù)。
public CancellationToken();//因?yàn)槭墙Y(jié)構(gòu)體,才有空構(gòu)造函數(shù),不過(guò)沒什么作用 public CancellationToken(bool canceled);
屬性如下:
//靜態(tài)屬性,獲取一個(gè)空的CancellationToken,這個(gè)CancellationToken注冊(cè)的回調(diào)方法不會(huì)被觸發(fā),作用類似于使用空構(gòu)造函數(shù)得到的CancellationToken public static CancellationToken None { get; } //表示當(dāng)前CancellationToken是否可以被取消 public bool CanBeCanceled { get; } //表示當(dāng)前CancellationToken是否已經(jīng)是取消狀態(tài) public bool IsCancellationRequested { get; } //和CancellationToken關(guān)聯(lián)的WaitHandle對(duì)象,CancellationToken注冊(cè)的回調(diào)方法執(zhí)行時(shí)通過(guò)這個(gè)WaitHandle實(shí)現(xiàn)的 public WaitHandle WaitHandle { get; }
常用方法:
//往CancellationToken中注冊(cè)回調(diào) public CancellationTokenRegistration Register(Action callback); public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext); public CancellationTokenRegistration Register([NullableAttribute(new[] { 1, 2 })] Action<object?> callback, object? state); public CancellationTokenRegistration Register([NullableAttribute(new[] { 1, 2 })] Action<object?> callback, object? state, bool useSynchronizationContext); //當(dāng)CancellationToken處于取消狀態(tài)是,拋出System.OperationCanceledException異常 public void ThrowIfCancellationRequested();
常用的注冊(cè)回調(diào)的方法是上面4個(gè)Register方法,其中callback是回調(diào)執(zhí)行的委托,useSynchronizationContext表示是否使用同步上下文,state是往回調(diào)委托中傳的參數(shù)值
另外,Register方法會(huì)返回一個(gè)CancellationTokenRegistration結(jié)構(gòu)體,當(dāng)注冊(cè)回調(diào)之后,可以調(diào)用CancellationTokenRegistration的Unregister方法來(lái)取消注冊(cè),這個(gè)Unregister方法會(huì)返回一個(gè)bool值,當(dāng)成功取消時(shí)返回true,當(dāng)取消失?。ū热缁卣{(diào)已執(zhí)行)將返回false:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenRegistration = cancellationTokenSource.Token.Register(() => { Console.WriteLine("Canceled");//這里將不會(huì)執(zhí)行輸出 }); //cancellationTokenSource.Cancel(); //var result = cancellationTokenRegistration.Unregister();//result = false var result = cancellationTokenRegistration.Unregister();//result = true cancellationTokenSource.Cancel();
上面提到,CancellationToken可以使用構(gòu)造函數(shù)直接構(gòu)造,同時(shí)可以傳入一個(gè)參數(shù),表示當(dāng)前的狀態(tài),需要注意的是,CancellationToken的狀態(tài)最多可以改變一次,也就是從未取消變成已取消。
如果構(gòu)造時(shí)傳入true,也就是說(shuō)CancellationToken是已取消狀態(tài),這個(gè)時(shí)候注冊(cè)的回調(diào)都會(huì)立即執(zhí)行:
CancellationToken cancellationToken = new CancellationToken(true); cancellationToken.Register(() => { Console.WriteLine("Canceled");//這里會(huì)立即執(zhí)行輸出Canceled });
但如果構(gòu)造時(shí)傳入的是false,說(shuō)明CancellationToken處于未取消狀態(tài),這時(shí)候注冊(cè)的回到都會(huì)處于一個(gè)待觸發(fā)狀態(tài):
CancellationToken cancellationToken = new CancellationToken(false); cancellationToken.Register(() => { Console.WriteLine("Canceled");//這里不會(huì)立即執(zhí)行輸出 });
通過(guò)Register方法注冊(cè)的服務(wù)只會(huì)執(zhí)行一次!
但一般的,如果傳入false構(gòu)造出來(lái)的CancellationToken,可以認(rèn)為是不會(huì)觸發(fā)的,因?yàn)樗鼪]有觸發(fā)的方法!所以一般的,我們都不會(huì)直接使用構(gòu)造函數(shù)創(chuàng)建CancellationToken,而是使用CancellationTokenSource對(duì)象來(lái)獲取一個(gè)CancellationToken
CancellationTokenSource
CancellationTokenSource可以理解為CancellationToken的控制器,控制它什么時(shí)候變成取消狀態(tài)的一個(gè)對(duì)象,它有一個(gè)CancellationToken類型的屬性Token,只要CancellationTokenSource創(chuàng)建,這個(gè)Token也會(huì)被創(chuàng)建,同時(shí)Token會(huì)和這個(gè)CancellationTokenSource綁定
//表示Token是否已處于取消狀態(tài) public bool IsCancellationRequested { get; } //CancellationToken 對(duì)象 public CancellationToken Token { get; }
可以直接創(chuàng)建一個(gè)CancellationTokenSource對(duì)象,同時(shí)指定一個(gè)時(shí)間段,當(dāng)過(guò)了這段時(shí)間后,CancellationTokenSource就會(huì)自動(dòng)取消了。
CancellationTokenSource的取消有4個(gè)方法:
//立刻取消 public void Cancel(); //立刻取消 public void Cancel(bool throwOnFirstException); //延遲指定時(shí)間后取消 public void CancelAfter(int millisecondsDelay); //延遲指定時(shí)間后取消 public void CancelAfter(TimeSpan delay);
Cancel和兩個(gè)CancelAfter方法沒什么特別的,主要就是有一個(gè)延遲的效果,需要注意的是Cancel的兩個(gè)重載之間的區(qū)別。
首先,上面說(shuō)道,CancellationToken狀態(tài)只能改變一次(從未取消變成已取消),當(dāng)CancellationToken時(shí)已取消狀態(tài)時(shí),每次往其中注冊(cè)的回調(diào)都會(huì)立刻執(zhí)行!當(dāng)處于未取消狀態(tài)時(shí),注冊(cè)進(jìn)去的回調(diào)都會(huì)等待執(zhí)行。
需要注意的是,當(dāng)在未取消狀態(tài)下注冊(cè)多個(gè)回調(diào)時(shí),它們?cè)趫?zhí)行時(shí)是一個(gè)類似棧的結(jié)構(gòu)順序,先注冊(cè)后執(zhí)行。
而CancellationToken的Register可以注冊(cè)多個(gè)回調(diào),那他們可能都會(huì)拋出異常,throwOnFirstException參數(shù)表示在第一次報(bào)錯(cuò)時(shí)的處理行為.
throwOnFirstException = true 表示立即拋出當(dāng)前發(fā)生的異常,后續(xù)的回調(diào)將會(huì)取消執(zhí)行
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); try { cancellationTokenSource.Token.Register(() => { throw new Exception("1"); }); cancellationTokenSource.Token.Register(() => { throw new Exception("2");//不會(huì)執(zhí)行 }); cancellationTokenSource.Cancel(true); } catch (Exception ex) { //ex is System.Exception("1") }
throwOnFirstException = false 表示跳過(guò)當(dāng)前回調(diào)的異常,繼續(xù)執(zhí)行生效的回調(diào),等所有的回調(diào)執(zhí)行完成之后,再將所有的異常打包成一個(gè)System.AggregateException異常拋出來(lái)!
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); try { cancellationTokenSource.Token.Register(() => { throw new Exception("1"); }); cancellationTokenSource.Token.Register(() => { throw new Exception("2"); }); cancellationTokenSource.Cancel(false);//相當(dāng)于cancellationTokenSource.Cancel() } catch (Exception ex) { //ex is System.AggregateException:[Exception("2"),Exception("1")] }
CancellationTokenSource還可以與其它CancellationToken關(guān)聯(lián)起來(lái),生成一個(gè)新的CancellationToken,當(dāng)其他CancellationToken取消時(shí),會(huì)自動(dòng)觸發(fā)當(dāng)前的CancellationTokenSource執(zhí)行取消動(dòng)作!
使用場(chǎng)景一
當(dāng)我們創(chuàng)建異步操作時(shí),可以傳入一個(gè)CancellationToken,當(dāng)異步操作處于等待執(zhí)行狀態(tài)時(shí),可以通過(guò)設(shè)置CancellationToken為取消狀態(tài)將異步操作取消執(zhí)行:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); var task = new Task(() => { Thread.Sleep(1500);//執(zhí)行了2秒中代碼 Console.WriteLine("Execute Some Code"); }, cancellationTokenSource.Token); task.Start();//啟動(dòng),等待調(diào)度執(zhí)行 //發(fā)現(xiàn)不對(duì),可以取消task執(zhí)行 cancellationTokenSource.Cancel(); Thread.Sleep(1000);//等待1秒 Console.WriteLine("Task狀態(tài):" + task.Status);//Canceled
但是經(jīng)常的,我們的取消動(dòng)作可能不會(huì)那么及時(shí),如果異步已經(jīng)執(zhí)行了,再執(zhí)行取消時(shí)無(wú)效的,這是就需要我們自己在異步委托中檢測(cè)了:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); var task = new Task(() => { Thread.Sleep(1500);//執(zhí)行了2秒中代碼 cancellationTokenSource.Token.ThrowIfCancellationRequested(); Console.WriteLine("Execute Some Code"); }, cancellationTokenSource.Token); task.Start();//啟動(dòng),等待調(diào)度執(zhí)行 Thread.Sleep(1000);////一段時(shí)間后發(fā)現(xiàn)不對(duì),可以取消task執(zhí)行 cancellationTokenSource.Cancel(); Thread.Sleep(1000);//等待1秒 Console.WriteLine("Task狀態(tài):" + task.Status);//Canceled
使用場(chǎng)景二
有時(shí),我們希望在觸發(fā)某個(gè)時(shí)間后,可以執(zhí)行某些代碼功能,但是在異步環(huán)境下,我們不能保證那些要執(zhí)行的代碼是否已準(zhǔn)備好了,比如我們有一個(gè)Close方法,當(dāng)調(diào)用Close后表示是關(guān)閉狀態(tài),如果我們相當(dāng)程序處于關(guān)閉狀態(tài)時(shí)執(zhí)行一些通知,一般的,我們可能是想到采用事件模型,或者在Close方法傳入事件委托,或者采用一些諸如模板設(shè)計(jì)這樣的模型去實(shí)現(xiàn):
class Demo { public void Close(Action callback) { //關(guān)閉 Thread.Sleep(3000); callback?.Invoke();//執(zhí)行通知 } }
或者
class Demo { public event Action Callback; public void Close() { //關(guān)閉 Thread.Sleep(3000); Callback?.Invoke();//執(zhí)行通知 } }
但是這就有問(wèn)題了,如果是傳入?yún)?shù)或者采用事件模型,因?yàn)榍懊嬲f(shuō)過(guò)了,如果在異步環(huán)境下,我們不能保證那些要執(zhí)行的代碼是否已準(zhǔn)備好了,也許在執(zhí)行Close方法時(shí),程序還未注冊(cè)回調(diào)。
這個(gè)時(shí)候就可以使用CancellationToken來(lái)解決這個(gè)問(wèn)題:
主需要往Token屬性中注冊(cè)回調(diào)而無(wú)需關(guān)注Close什么時(shí)候執(zhí)行了
使用場(chǎng)景三
有時(shí)候,我們寫一個(gè)異步無(wú)限循環(huán)的方法去處理一些問(wèn)題,而我們希望可以在方法外來(lái)停止它這個(gè)時(shí)候,我們就可以通過(guò)返回CancellationTokenSource來(lái)實(shí)現(xiàn)了:
public CancellationTokenSource Listen() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); //循環(huán)調(diào)度執(zhí)行 Task.Run(() => { while (true) { cancellationTokenSource.Token.ThrowIfCancellationRequested(); //循環(huán)執(zhí)行一些操作 Thread.Sleep(1000); Console.WriteLine("Run"); } }); return cancellationTokenSource; }
以上就是C# CancellationToken和CancellationTokenSource的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于C# CancellationToken和CancellationTokenSource的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C# 延遲Task.Delay()和Thread.Sleep()的具體使用
Thread.Sleep()是同步延遲,Task.Delay()是異步延遲,本文主要介紹了C# 延遲Task.Delay()和Thread.Sleep()的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01c#通過(guò)app.manifest使程序以管理員身份運(yùn)行
通常我們使用c#編寫的程序不會(huì)彈出這個(gè)提示,也就無(wú)法以管理員身分運(yùn)行。微軟的操作系統(tǒng)使用微軟的產(chǎn)品方法當(dāng)然是有的,通過(guò)app.manifest配置可以使程序打開的時(shí)候,彈出UAC提示需要得到允許才可以繼續(xù),這樣就獲得了管理員的權(quán)限來(lái)執(zhí)行程序2015-01-01詳解C#借助.NET框架中的XmlTextReader類讀取XML的方法
這篇文章主要介紹了詳解借助.NET框架中的XmlTextReader類讀取XML的方法,這種方式的執(zhí)行效率還是比較令人滿意的,需要的朋友可以參考下2016-04-04