.Net 並行編程系列之 Task取消


在Task運行過程中,我們可以通過.Net 4中的內置方法來取消Task的運行。

創建一個可取消的Task需要用到下面的一些對象:

1.System.Threading.CancellationTokenSource實例

CancellationTokenSource tokenSource = new CancellationTokenSource();

2.通過CancellationTokenSource.Token屬性獲得一個取消令牌

CancellationToken token = tokenSource.Token;

3.創建Task對象,並且在構造函數傳入Action(或者Action<T>)委托作為第一個參數,CancellationToken作為第二個參數(重要)

Task task = new Task(() =>
{
// do something
}, token);
task.Start();

 4.創建Task對象也可以通過調用System.Threading.Tasks.TaskFactory 類提供的靜態方法

Task task = Task.Factory.StartNew(() =>
{
    // do something ......
}, token);

 

如果想要取消Task的運行,除了要調用CancellationTokenSource實例的Cancel()方法之外,我們的Action委托中還需要檢測CancellationToken的取消狀態並編寫相應代碼(拋出異常)來阻止Task的運行。

可以通過以下方式來檢測Task取消狀態:

1.通過輪詢的方式檢測CancellationToken取消標記,該操作類似於輪詢異步操作的IAsyncResult.IsCompleted狀態,也是通過在循環中判斷CancellationToken.IsCancellationRequested屬性來檢測Task是否被取消,如果為True則在Action委托中拋出異常來取消繼續運行Task。 

static void Main(string[] args)
{
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;
    Task task = new Task(() =>
    {
        while (true)
        {
            if(token.IsCancellationRequested)
            {
                // 釋放資源操作等等...
                throw new OperationCanceledException(token);
            }
            Console.Write(".");
            Thread.Sleep(100);
        }
    }, token);

    Console.WriteLine("Task is Running.");
    Console.WriteLine("Press anykey to cancel task.");    
task.Start();
Console.ReadKey(
true); Console.WriteLine(); Console.WriteLine("Cancelling task."); tokenSource.Cancel();
Console.WriteLine(
"Main method complete."); Console.WriteLine("Press enter to finish."); Console.ReadLine(); }

 

如果不需要釋放系統資源,那么可以直接調用CancellationToken.ThrowIfCancellationRequested()方法,其實現如下:

[__DynamicallyInvokable]
public void ThrowIfCancellationRequested()
{
    if (this.IsCancellationRequested)
    {
        this.ThrowOperationCanceledException();
    }
}

 示例:

while (true)
{
    token.ThrowIfCancellationRequested();
    Console.Write(".");
    Thread.Sleep(100);
}

 

2.通過委托(Delegate)來檢測Task是否取消,注冊一個在取消CancellationToken時調用的委托,當CancellationTokenSource發送取消請求時,該委托即會運行,我們可以在委托方法中實現通知功能等等。

static void Main(string[] args)
{
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;
    Task task = new Task(() =>
    {
        while (true)
        {
            token.ThrowIfCancellationRequested();
            Console.Write(".");
            Thread.Sleep(100);
        }
    }, token);

    token.Register(() => { Console.WriteLine("The delegate is triggered."); });

    Console.WriteLine("Task is Running.");
    Console.WriteLine("Press anykey to cancel task.");

    task.Start();

    Console.ReadKey(true);
    Console.WriteLine();
    Console.WriteLine("Cancelling task.");
    tokenSource.Cancel();

    Console.WriteLine("Main method complete.");
    Console.WriteLine("Press enter to finish.");
    Console.ReadLine();
}

 

3.用WaitHandle來檢測Task是否取消,當在CancellationToken.WaitHandle上調用WaitOne()方法時,會阻止當前線程執行,直到該CancellationToken接收到取消請求標記時,被token阻止的Task線程才會釋放並繼續執行。

多個Task實例使用同一個CancellationToken時,當CancellationToken接收到取消請求標記時,所有在構造函數中使用該token實例化的Task都會被取消。WaitOne(int millisecondsTimeout)可以使用等待毫秒數作為參數,超過等待時間將會釋放阻止的線程。

不管WaitOne(int millisecondsTimeout)設置多長的等待時間,只要CancellationToken接收到取消請求標記時Task都會取消,而如果使用Thread.Sleep(100000)進行線程等待時,那么即使CancellationToken接收到取消請求標記,該Task也會等到Thread.Sleep執行完成才會Cancel。

static void Main(string[] args)
{
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;

    Task task = new Task(() =>
    {
        while (true)
        {                    
            token.ThrowIfCancellationRequested();
            Console.Write(".");
            Thread.Sleep(100);
        }
    }, token);

    Task task1 = new Task(() =>
    {
        token.WaitHandle.WaitOne();
        Console.WriteLine("WaitHandle released.");
    }, token);

    Console.WriteLine("Task is Running.");
    Console.WriteLine("Press anykey to cancel task.");

    task.Start();
    task1.Start();

    Console.ReadKey(true);
    Console.WriteLine();
    Console.WriteLine("Cancelling task.");
    tokenSource.Cancel();
Console.WriteLine(
"Main method complete."); Console.WriteLine("Press enter to finish."); Console.ReadLine(); }

 

4.通過Task的IsCancelled屬性來判斷Task是否被取消,如果Task實例化時構造函數沒有傳入CancellationToken對象,則取消Task運行之后通過Task.IsCanceled屬性獲取到的值還是False而不是TrueTask.ContinueWith()方法如果沒有傳入CancellationToken對象,則Task即使是取消執行也會繼續執行Task.ContinueWith()方法的Action委托,如果傳入與Task相同的CancellationToken對象,則Task取消執行后Task.ContinueWith()方法中的Action委托也不會繼續執行。

{
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;
    Task task = new Task(() =>
    {
        try
        {
            Console.WriteLine("Task: Running");
            Thread.Sleep(5000);
            Console.WriteLine("Task: ThrowIfCancellationRequested");
            token.ThrowIfCancellationRequested();
            Thread.Sleep(2000);
            Console.WriteLine("Task: Completed");
        }
        catch (Exception exception)
        {
            Console.WriteLine("Task: " + exception.GetType().Name);
            throw;
        }
    }, token);

    task.ContinueWith(t => Console.WriteLine("ContinueWith: tokenSource.IsCancellationRequested = {0}, task.IsCanceled = {1}, task.Exception = {2}", tokenSource.IsCancellationRequested, t.IsCanceled, t.Exception == null ? "null" : t.Exception.GetType().Name));
    task.Start();
    
    Thread.Sleep(1000);

    Console.WriteLine("Main: Cancel");
    tokenSource.Cancel();

    try
    {
        Console.WriteLine("Main: Wait");
        task.Wait();
    }
    catch (Exception exception)
    {
        Console.WriteLine("Main: Catch " + exception.GetType().Name);
    }

    Console.WriteLine("Main: task.IsCanceled = {0}", task.IsCanceled);
    Console.WriteLine("Press any key to exit...");

    Console.ReadKey(true);
}

 

通過執行Task.WaitAll(task),Task.WaitAny(task),task.Result,task.Wait()出現了異常拋出的是一個System.AggregateException;

通過執行task.Wait(CancellationToken)出現了異常拋出的是一個OperationCanceledException;


免責聲明!

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



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