C#CancellationToken/CancellationTokenSource-取消令牌/取消令牌源 CT/CTS


詳細情況:https://www.cnblogs.com/wucy/p/15128365.html

背景

為什么引入取消令牌?

Thread.abort()方法會破壞同步鎖中代碼的原子邏輯,破壞鎖的作用。以下代碼說明了Thread.abort()方是如何破壞鎖的 :
代碼的功能:每個線程進入鎖內都會休息10s。

int a = 0;
void RelaxMoment()
{
    lock ("")
    {

        a++;
        if (a == 1)
        {
            Thread.Sleep(10000);
            a--;
        }

    }
}

線程A正在鎖中sleep中突然被主線程abort停止了,此時a=1。直接導致后續進入鎖的線程無法休息。
既然終止一個線程不能使用abort方法,那怎樣才能終止一個正在運行的線程呢?答案也很簡單,使用自定義的標志位決定線程的執行情況,工作線程內程序員根據標志自行判讀該合理的停止的位置,所以就引入標志----取消令牌CancellationToken

CancellationTokenSource原理刨析

CTS是用來發出取消標記,線程和任務根據CTS發出的信號,在自行決定在合適的位置取消線程或任務。

CTS是發出取消標記方式有2種 自動和手動:

1、手動通過調用Cancled()發出取消標記。 

2、自動方式通過定時器、指定時間后取消信號。

重要的內部成員

ManualResetEvent? _kernelEvent;  :取消線程暫停

 int _state :標記取消

屬性

    IsCancellationRequested:判斷IsCancellationRequested => _state != NotCanceledState;
    Token:封裝CTS,返回對CTS操縱源CT,俗稱取消令牌

 方法

Cancel()

1、將取消標記(_state=1)設置為 "已取消" 狀態。 int _state=1; 2 、 TimerQueueTimer? timer.Close(); 取消隊列 3、 _kernelEvent?.Set(); //將內部的ManualResetEvent設置為set() 4、ExecuteCallbackHandlers(bool throwOnFirstException);//執行ct.Register(委托)中注冊的委托

CreateLinkedTokenSource():將多個CTS鏈接一起 形成新的CTS,相當於總開關。

CancelAfter(Int32/TimeSpan) :指定過多長時間后取消

Dispose():由於CTS內部有一個內核對象ManualResetEvent實例  所以需要釋放,一般GC會自動回收,不用調用該方法。

CancellationToken原理刨析

取消令牌(CT)只是用來封裝取消令牌源(CTS)的。線程/任務中通過CT接收CTS源發出的set信號或通過CT不停的偵聽CTS狀態,判斷是否發出取消消息。如果發出取消消息,那么線程/任務在自行決定在合適的位置取消線程或任務。

CT內部封裝了一個cts 實例的引用。CTS內部封裝了一個 ManualResetEvent 實例對象和int型取消狀態 。 線程/任務通過CT內部cts 對象發出的信號,來自行決定在合適的位置取消線程或任務

屬性

WaitHandle:返回cts.ManualResetEvent 實例。
None:default
IsCancellationRequested:cts.IsCancellationRequested => cts != null && cts.IsCancellationRequested; 然后可以拋出  ThrowOperationCanceledException();結束線程或任務。

CanBeCanceled:CanBeCanceled => _cts != null;

方法

ThrowIfCancellationRequested()源碼:

 public void ThrowIfCancellationRequested()
        {
            if (IsCancellationRequested)
                ThrowOperationCanceledException();
        }

 Register():從這個最底層的方法我們可以得知,其本質還是調用CancellationTokenSource的InternalRegister方法,核心操作都不在CancellationToken還是在CancellationTokenSource類,源代碼如下:

private CancellationTokenRegistration Register(Delegate callback!!, object? state, bool useSynchronizationContext, bool useExecutionContext)
        {
            CancellationTokenSource? source = _source;
            return source != null ?
                source.Register(callback, state, useSynchronizationContext ? SynchronizationContext.Current : null, useExecutionContext ? ExecutionContext.Capture() : null) :
                default; // Nothing to do for tokens than can never reach the canceled state. Give back a dummy registration.
        }

 

CancellationTokenSource 取消任務用法詳解

CancellationTokenSource.Cancel不代表立即終止代碼。它只是通知工作線程 “你可以結束了”。工作線程在關鍵的代碼行中插入監控代碼,判斷任務是否被取消,這類似於軟件工程中的“埋點”,
用戶可以在何理的位置拋出異常結束線程。並且在取消任務時候通過CancellationToken.Register處理收尾工作。

if (ct.IsCancellationRequested)
{
    Console.WriteLine("Task {0} cancelled", taskNum);
    ct.ThrowIfCancellationRequested();
        //此方法提供等效於的功能:
        //if (token.IsCancellationRequested)   
        //throw new OperationCanceledException(token);  
}

 

相關的類

CancellationTokenSource 類
CancellationToken 結構
CancellationTokenRegistration 結構
WaitHandle 類
ManualResetEvent 類

取消任務

1、定時取消

創建 CancellationTokenSource 的時候,可以傳入時間(毫秒或者Timespan), 通過它我們可以在等待一段時間后,自動取消任務。

CancellationTokenSource cts = new CancellationTokenSource(1000);  
_ = Execute(cts.Token);  
Console.ReadKey();

我們也可以調用 cts.CancelAfter(1000), 它會在1s后取消任務。

2、第二種方式定時取消任務

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;
cancellationToken.Register(() => System.Console.WriteLine("被取消了."));//要在取消 CancellationToken 時執行的委托。
tokenSource.CancelAfter(5000);
while (true)
{
    //如果操作被取消則直接拋出異常
    cancellationToken.ThrowIfCancellationRequested();
    System.Console.WriteLine("一直在執行...");
    await Task.Delay(1000);
}
/*
 *輸出
一直在執行...
一直在執行...
一直在執行...
一直在執行...
一直在執行...
被取消了.
*/

 

3、具有等待句柄 取消任務

 int eventThatSignaledIndex =WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },new TimeSpan(0, 0, 20));
    if (eventThatSignaledIndex == 1)
    {
      Console.WriteLine("The wait operation was canceled.");
            throw new OperationCanceledException(token);
            //其他代碼

 4、關聯取消

將cts1、cts2、cts3多個取消令牌源關聯在一起ctsLink,形成新的取消令牌源。 只要cts1、cts2、cts3其中一個令牌發出取消命令,那么ctsLink取消。

//聲明幾個CancellationTokenSource
CancellationTokenSource cts1 = new ();
CancellationTokenSource cts2 = new ();
CancellationTokenSource cts3 = new();

cts2.Token.Register(() => System.Console.WriteLine("tokenSource2被取消了"));
cts1.Token.Register(() => System.Console.WriteLine("tokenSource1被取消了"));
//創建一個關聯的CancellationTokenSource
var ctsLink = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token, cts3.Token);
ctsLink.Token.Register(() => System.Console.WriteLine("ctsLink被取消了"));
//取消tokenSource2
cts3.Cancel();
//輸出 ctsLink被取消了

5、不允許被取消的操作

要執行一個不允許被取消的操作,可向該操作傳遞通過調用CancellationToken的靜態None屬性而返回的CancellationToken。
 該屬性返回一個特殊的CancellationToken實例,它不和任何CancellationTokenSource對象關聯(實例的私有字段為null)。
由於沒有CancellationTokenSource,所以沒有代碼能調用Cancel .一個操作如果查詢這個特殊CancellationToken的 IsCancellationRequested屬性,將總是返回false.使用某個特殊 CancellationToken實例查詢CancellationToken的CanBeCanceled屬性,屬性會返回false。
相反,對於通過查詢CancellationTokenSource對象的 Token屬性而獲得的其他所有CancellationToken實例,該屬性(CanBeCanceled)都會返回 true.

CancellationToken 注冊回調

我們可以調用 Register()方法,注冊Token取消的回調,參數需要傳入 Action 委托。

CancellationTokenSource cts = new CancellationTokenSource(1000);
cts.Token.Register(() => Console.WriteLine("任務已取消!"));
// 開始異步任務
_ = Execute(cts.Token);    
Console.ReadKey();

Register() 注冊回調后,返回一個 CancellationTokenRegistration 對象,同樣的,你可以在回調函數執行前,移除注冊回調,就像這樣:

CancellationTokenSource tokenSource = new  ();
CancellationTokenRegistration cancellationTokenRegistration = tokenSource.Token.Register(() => Console.WriteLine("")); 
cancellationTokenRegistration.Unregister();
tokenSource.Cancel();

 

 

 


免責聲明!

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



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