相關類型:
- CancellationTokenSource 主要用來創建或取消令牌
- CancellationToken 監聽令牌狀態,注冊令牌取消事件
- OperationCanceledException 令牌被取消時拋出的異常,可以由監聽者自主決定是否拋出異常
CancellationTokenSource
創建令牌:
1
2
3
|
CancellationTokenSource cts = new CancellationTokenSource() CancellationToken token=cts.Token; |
取消釋放令牌:
1
|
cts.Cancel(); |
CancellationToken
監聽令牌取消事件:
1
|
token.Register(() => Console.WriteLine( "令牌被取消" )); |
判斷令牌是否取消
1
2
3
4
5
6
7
8
9
|
//返回一個bool,如果令牌被取消為true token.IsCancellationRequested //如果token被取消則拋出異常,內部實現其實就是判斷IsCancellationRequested token.ThrowIfCancellationRequested()=>{ if (token.IsCancellationRequested){ throw new OperationCanceledException(); } } |
代碼示例
下面模擬一個文件下載的任務,在未下載完成后下載任務被取消
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public void Run() { CancellationTokenSource cts = new CancellationTokenSource(); Task.Run(() => { //等待兩秒后取消,模擬的是用戶主動取消下載任務 Thread.Sleep(2000); cts.Cancel(); }); try { var size = DownloadFile(cts.Token); Console.WriteLine( "文件大小:" + size); } catch (OperationCanceledException) { Console.WriteLine( "下載失敗" ); } finally { cts.Dispose(); } Thread.Sleep(2000); } /// <summary> /// 模擬下載文件,下載文件需要五秒 /// </summary> /// <returns></returns> public int DownloadFile(CancellationToken token) { token.Register(() => { System.Console.WriteLine( "監聽到取消事件" ); }); Console.WriteLine( "開始下載文件" ); for ( int i = 0; i < 5; i++) { token.ThrowIfCancellationRequested(); Console.WriteLine(i.ToString()); Thread.Sleep(1000); } Console.WriteLine( "文件下載完成" ); return 100; } |
輸出結果:
開始下載文件
0
1
監聽到取消事件
下載失敗
思考
為什么要將CancellationToken和CancellationTokenSource分為兩個類呢,直接一個CancellationToken又可以取消又可以判斷狀態注冊啥的不是更好,更方便?
其實每種類的設計和實現都可以有很多不同的策略,CTS和CT從這個兩個類提供的為數不多的公開方法中就可以看出,CTS用來控制Token的生成和取消等生命周期狀態,CT只能用來監聽和判斷,無法對Token的狀態進行改變。
所以這種設計的目的就是關注點分離。限制了CT的功能,避免Token在傳遞過程中被不可控的因素取消造成混亂。
關聯令牌
繼續拿上面的示例來說,示例中實現了從外部控制文件下載功能的終止。
如果要給文件下載功能加一個超時時間的限制,此時可以增加一個控制超時時間的token,將外部傳來的token和內部token 關聯起來變為一個token
只需要將DownloadFile()函數做如下改造即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public int DownloadFile(CancellationToken externalToken) { //通過構造函數設置TokenSource一秒之后調用Cancel()函數 var timeOutToken = new CancellationTokenSource( new TimeSpan(0, 0, 1)).Token; using (var linkToken = CancellationTokenSource.CreateLinkedTokenSource(externalToken, timeOutToken)) { Console.WriteLine( "開始下載文件" ); for ( int i = 0; i < 5; i++) { linkToken.Token.ThrowIfCancellationRequested(); Console.WriteLine(i.ToString()); Thread.Sleep(1000); } Console.WriteLine( "文件下載完成" ); return 100; } } |
此時不論是externalToken取消,或是timeOutToken取消,都會觸發linkToken的取消事件
CancellationChangeToken
CancellationChangeToken主要用來監測目標變化,需配合ChangeToken使用。從功能場景來說,其實ChangeToken的功能和事件似乎差不多,當監控的目標發生了變化,監聽者去做一系列的事情。
但是事件的話,監聽者需要知道目標的存在,就是如果A要注冊B的事件,A是要依賴B的。
CancellationChangeToken是基于CancellationToken來實現的,可以做到依賴于Token而不直接依賴被監聽的類
創建CancellationChangeToken:
1
|
new CancellationChangeToken( new CancellationTokenSource().Token) |
監聽Token變動
1
|
new CancellationChangeToken(cts.Token).RegisterChangeCallback(obj => Console.WriteLine( "token 變動" ), null ); |
CancellationChangeToken只是把CancellationToken包裝了一層。RegisterChangeCallback最終也是監聽的CancellationToken的IsCancellationRequested狀態。
所以就有個問題,代碼寫到這里,并不能實現每次內部變動都觸發回調事件。
因為CT只會Cancel一次,對應的監聽也會執行一次。無法實現多次監聽
為了實現變化的持續監聽,需要做兩個操作
- 讓Token在Cancel之后重新初始化
- 每次Cancel回調之后重新監聽新的Token
先上代碼,下面的代碼實現了每次時間變動都會通知展示面板刷新時間的顯示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public void Run() { var bjDate = new BeijingDate(); DisplayDate(bjDate.GetChangeToken, bjDate.GetDate); Thread.Sleep(50000); } public void DisplayDate(Func<IChangeToken> getChangeToken, Func<DateTime> getDate) { ChangeToken.OnChange(getChangeToken, () => Console.WriteLine( "當前時間:" + getDate())); } public class BeijingDate { private CancellationTokenSource cts; private DateTime date; public BeijingDate() { cts = new CancellationTokenSource(); var timer = new Timer(TimeChange, null , 0, 1000); } private void TimeChange( object state) { date = DateTime.Now; var old = cts; cts = new CancellationTokenSource(); old.Cancel(); } public DateTime GetDate() => date; public CancellationChangeToken GetChangeToken() { return new CancellationChangeToken(cts.Token); } } |
在TimeChange()中修改了時間,重置了Token并將舊的Token取消
在DisplayDate中用ChangeToken.OnChange獲取對應的Token并監聽
實現了DisplayData函數和BeijingDate這個類的解耦
ChangeToken.OnChange 這個函數接收兩個參數,一個是獲取Token的委托,一個是Token取消事件的響應委托。
每次在處理完Token的取消事件后,他會重新調用第一個委托獲取Token,而此時我們已經生成了新的Token,最終實現了持續監控
到此這篇關于.Net中異步任務的取消和監控的文章就介紹到這了,更多相關.Net中異步任務的取消和監控內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.cnblogs.com/bluesummer/p/15219701.html