.Net Core CancellationTokenSource的應用

可用來 控制多執行序停止和中斷和Cancel後執行的callback function

建立類別的三種多載Construct

第二種跟第三種多載的建構子參數是設定 多久後會取消Task, 單位為 millisecond(毫秒) 與 TimeSpan
IsCancellationRequested 會在delay時間過後為true,並執行Token中Register的Action。 (Token的Register 可以註冊 工作取消後會執行的動作)

public CancellationTokenSource();
public CancellationTokenSource(int millisecondsDelay);
public CancellationTokenSource(TimeSpan delay);

判斷該 CancellationTokenSource 是否被取消的Property IsCancellationRequested

當被Cancel掉時,該值為true

public bool IsCancellationRequested { get; }

建立具有監聽用途的CancellationTokenSource

該CancellationTokenSource的狀態是否被Cancel 是由 傳入的CancellationToken的狀態來決定,當傳入的任一CancellationToken被Cancel掉時,該CancellationTokenSource也同樣被Cancel掉。

public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2);
public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens);
/// <summary>
/// 建立有連結的CancellationTokenSource
/// 當用來建立的任一CancellationTokenSource被Cancel掉時,該LinkedTokenSource也同樣被Cancel掉
///
/// 測試呼叫 CreateLinkedTokenSource
/// </summary>
public static void CreateLinkedTokenSource()
{
    CancellationTokenSource notCancellationTokenSource = new CancellationTokenSource();
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

    CancellationTokenSource test = CancellationTokenSource.CreateLinkedTokenSource(notCancellationTokenSource.Token, cancellationTokenSource.Token);
    cancellationTokenSource.Cancel();

    Console.WriteLine($"notCancellationTokenSource類別是否被Cancel了 = {notCancellationTokenSource.IsCancellationRequested}");
    Console.WriteLine($"cancellationTokenSource類別是否被Cancel了 = {cancellationTokenSource.IsCancellationRequested}");
    Console.WriteLine($"該CancellationTokenSource類別是否被Cancel了 = {test.IsCancellationRequested}");
}

Cancel 掉 CancellationTokenSource

當CancellationTokenSource被Cancel掉時,已經執行中的Task並不會中斷
必須要自行在委派中判斷CancellationTokenSource.IsCancellationRequested or CancellationTokenSource.Token.IsCancellationRequested 是否為true
再處理中斷Task後的動作。

public void Cancel();
public void Cancel(bool throwOnFirstException);
/// <summary>
/// cancel掉Task
///
/// 測試呼叫 Cancel()
/// </summary>
public static void Cancel()
{
    stopwatch.Stop();
    stopwatch.Reset();
    stopwatch.Start();

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    CancellationToken a = new CancellationToken();
    Task.Run(() =>
    {
        while (true)
        {
            Thread.Sleep(500);

            if (cancellationTokenSource.Token.IsCancellationRequested)
            {
                Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task have canceled!{cancellationTokenSource.IsCancellationRequested}");

                return;
            }
            else
                Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Running!{cancellationTokenSource.IsCancellationRequested}");
        }
    }, cancellationTokenSource.Token);

    Thread.Sleep(5000);
    cancellationTokenSource.Cancel();
}

/// <summary>
/// 透過cancellationTokenSource設定Task被cancel掉所要執行的delegate
///
/// 測試呼叫 Cancel() & Cancel(bool throwOnFirstException)
/// 呼叫 Cancel() 會執行 Cancel(false)
/// 該boolean參數 用來判斷當有Register被Cancel掉要執行的動作噴Exception時
/// 若為是,代表直接噴Exception,不管其餘動作是否執行完畢。
/// 若為否,則會執行完其餘Register的動作之後才會噴Exception。
/// </summary>
public static void Cancel_Register()
{
    stopwatch.Stop();
    stopwatch.Reset();
    stopwatch.Start();

    try
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        // 註冊的動作是LIFO,最晚註冊的開始執行
        cancellationTokenSource.Token.Register(() => Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Canceled1"));
        cancellationTokenSource.Token.Register(() => Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Canceled2"));
        cancellationTokenSource.Token.Register(() => Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Canceled3"));
        cancellationTokenSource.Token.Register(() => Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Canceled4"));
        cancellationTokenSource.Token.Register(() => throw new Exception($"{stopwatch.ElapsedMilliseconds} ms Task Canceled"));
        cancellationTokenSource.Token.Register(() => Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Canceled5"));

        Task.Run(() =>
        {
            while (true)
            {
                Thread.Sleep(500);
                if (!cancellationTokenSource.IsCancellationRequested)
                    Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Running!");
            }
        }, cancellationTokenSource.Token);

        Thread.Sleep(5000);
        Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms 執行 Task Cancel(false)");
        cancellationTokenSource.Cancel();
        //cancellationTokenSource.Cancel(true);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"發生異常, Error:{ex.Message}");
    }
}

執行 Cancel() 也等於 Cancel(false),當Cancel後執行的Register有異常時會執行完所有Register

執行 Cancel(true),當Cancel後執行的Register有發生異常時,其餘的Register則不會執行

定時 Cancel 掉 CancellationTokenSource

有定時功能的Cancel

public void CancelAfter(int millisecondsDelay);
public void CancelAfter(TimeSpan delay);
/// <summary>
/// 透過cancellationTokenSource設定Task幾秒後會cancel掉
///
/// 測試呼叫 CancelAfter(int millisecondsDelay) & CancelAfter(TimeSpan delay)
/// millisecondsDelay 與 delay,代表多久要取消該Task
/// </summary>
public static void CancelAfter()
{
    stopwatch.Stop();
    stopwatch.Reset();
    stopwatch.Start();

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

    Task.Run(() =>
    {
        while (true)
        {
            Thread.Sleep(500);
            if (!cancellationTokenSource.IsCancellationRequested)
                Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Running!");
            else
                Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task have canceled!");
        }
    }, cancellationTokenSource.Token);

    //cancellationTokenSource.CancelAfter(3000);
    cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5));
}

Dispose CancellationTokenSource

當CancellationTokenSource被Dispose掉,則設定的Register和Cancel Timer都被清除
且只剩IsCancellationRequested可使用,若繼續呼叫Cancel() 則會噴Exception

public void Dispose();
/// <summary>
/// 測試呼叫 Dispose
/// 當CancellationTokenSource被Dispose掉,代表先前設定的Register和Cancel Timer都被清除
/// </summary>
public static void Dispose()
{
    stopwatch.Stop();
    stopwatch.Reset();
    stopwatch.Start();

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

    Task.Run(() =>
    {
        while (true)
        {
            Thread.Sleep(500);
            if (!cancellationTokenSource.IsCancellationRequested)
                Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Running!");
            else
                Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task have canceled!");
        }
    }, cancellationTokenSource.Token);

    cancellationTokenSource.Token.Register(() => Console.WriteLine($"{stopwatch.ElapsedMilliseconds} ms Task Register"));

    // 5秒後Cancel掉Task
    cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5));

    // 3秒後dispose掉CancellationTokenSource,清除掉CancelAfter的timer和Register動作
    Thread.Sleep(3000);
    cancellationTokenSource.Dispose();

    // 被dispose掉後除了IsCancellationRequested之外  其餘方法無法再調用,會噴Exception
    //cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(2));
}

程式碼 可參考 w4560000/CSharp_Practice/CancellationTokenSourceAPI


轉載請註明來源,若有任何錯誤或表達不清楚的地方,歡迎在下方評論區留言,也可以來信至 leozheng0621@gmail.com
如果文章對您有幫助,歡迎斗內(donate),請我喝杯咖啡

斗內💰

×

歡迎斗內

github