.NET 4并行編程入門之Task的取消
查看本系列其他文章,請參看
因為Task是.NET 4并行編程最為核心的一個類,也我們在是在并行編程常常打交道的類,所以,對Task對全面的了解很有必要。
上篇文章主要講述了如何創(chuàng)建一個task,本篇文章主要講述如何取消一個task。
本篇主的主要議題如下:
1. 通過輪詢的方式檢測Task是否被取消
2. 用委托delegate來檢測Task是否被取消
3. 用Wait Handle還檢測Task是否被取消
4. 取消多個Task
5. 創(chuàng)建組合的取消Task的Token
6. 判斷一個Task是否已經(jīng)被取消了
本篇的理論不多,代碼的例子很多。
在TPL中一個標(biāo)準(zhǔn)化的操作就是”取消Task”。之所以說它是個標(biāo)準(zhǔn)化的操作,其實是把這個操作和之前傳統(tǒng)的多線程編程進行比較而言的。
在之前的多線程編程中,我們一般是自己寫一些代碼來取消線程的運行。但是在.NET 4的TPL中就內(nèi)置了取消的方法,可能我們覺得TPL沒有必要內(nèi)置這些代碼,因為太簡單了。但是這個內(nèi)置的方法不僅僅只是取消了運行的Task,而且還減小了在取消運行的Task時可能產(chǎn)生的一些風(fēng)險,我們后續(xù)文章會詳細講述。
創(chuàng)建一個取消的Task一般要進行下面一些步驟:
a.創(chuàng)建System.Threading.CancellationTokenSource的一個實例:
- // create the cancellation token source
- ancellationTokenSource tokenSource = new CancellationTokenSource();
b.通過CancellationTokenSource.Token屬性獲得一個System.Threading.CancellationToken:
- CancellationToken token = tokenSource.Token;
c.創(chuàng)建一個新的Task或者Task<T>,并且在構(gòu)造函數(shù)傳入Action或者Action<object>的委托作為***個參數(shù),傳入CancellationToken作為第二個參數(shù):
- Task task = new Task(new Action(printMessage), token);
d.調(diào)用Task的Start()方法。
上面的步驟和我們之前介紹的創(chuàng)建一個Task的代碼幾乎一樣,只是在構(gòu)造函數(shù)中多傳入了一個參數(shù)。
如果想要取消一個Task的運行,只要調(diào)用CancellationToken實例的Cancel()方法就可以了。
有點要特別注意的,當(dāng)我們調(diào)用了Cancel()方法之后,.NET Framework不會強制性的去關(guān)閉運行的Task。
我們自己必須去檢測之前在創(chuàng)建Task時候傳入的那個CancellationToken。
我們在創(chuàng)建Task是傳入CancellationToken到構(gòu)造函數(shù),其實這個CancellationToken就是.NET Framework用來避免我們再次運行已經(jīng)被取消的Task,可以說就是一個標(biāo)志位。
首先,進入***個議題:
1.通過輪詢的方式檢測Task是否被取消
在很多Task內(nèi)部都包含了循環(huán),用來處理數(shù)據(jù)。我們可以在循環(huán)中通過CancellationToken的IsCancellationRequest屬性來檢測task是否被取消了。如果這個屬性為true,那么我們就得跳出循環(huán),并且釋放task所占用的資源(如數(shù)據(jù)庫資源,文件資源等).
我們也可以在task運行體中拋出System.Threading.OperationCanceledException來取消運行的task。
代碼如下:
代碼
- while (true)
- {
- if (token.IsCancellationRequested)
- {
- // tidy up and release resources
- throw new OperationCanceledException(token);
- }
- else
- {
- // do a unit of work
- }
- }
如果我們沒有任何的資源要釋放,那么只要簡單的調(diào)用CancellationToken.ThrowIfCancellationRequested()方法,這個方法會檢查是否要取消task,并且拋出異常。代碼如下:
- while (true)
- token.ThrowIfCancellationRequested();
- // do a unit of work
- }
下面就給出有一個完整的例子:創(chuàng)建一個可以取消的task,并且通過輪詢不斷的檢查是否要取消task
代碼如下:
代碼
- static void Main(string[] args)
- {
- // create the cancellation token source
- CancellationTokenSource tokenSource = new CancellationTokenSource();
- // create the cancellation token
- CancellationToken token = tokenSource.Token;
- // create the task
- Task task = new Task(() =>
- {
- for (int i = 0; i < int.MaxValue; i++)
- {
- if (token.IsCancellationRequested)
- {
- Console.WriteLine("Task cancel detected");
- throw new OperationCanceledException(token);
- }
- else
- {
- Console.WriteLine("Int value {0}", i);
- }
- }
- }, token);
- // wait for input before we start the task
- Console.WriteLine("Press enter to start task");
- Console.WriteLine("Press enter again to cancel task");
- Console.ReadLine();
- // start the task
- task.Start();
- // read a line from the console.
- Console.ReadLine();
- // cancel the task
- Console.WriteLine("Cancelling task");
- tokenSource.Cancel();
- // wait for input before exiting
- Console.WriteLine("Main method complete. Press enter to finish.");
- Console.ReadLine();
- }
2. 用委托delegate來檢測Task是否被取消
我們可以在注冊一個委托到CancellationToken中,這個委托的方法在CancellationToken.Cancel()調(diào)用之前被調(diào)用。
我們可以用這個委托中的方法來作為一個檢測task是否被取消的另外一個可選的方法,因為這個方法是在Cancel()方法被調(diào)用之前就調(diào)用的,所以這個委托中的方法可以檢測task是否被cancel了,也就是說,只要這個委托的方法被調(diào)用,那么就說這個CancellationToken.Cancel()方法被調(diào)用了,而且在這個委托的方法中我們可以做很多的事情,如通知用戶取消操作發(fā)生了。
下面的代碼給出了一個例子。
代碼
- static void Main(string[] args)
- {
- // create the cancellation token source
- CancellationTokenSource tokenSource = new CancellationTokenSource();
- // create the cancellation token
- CancellationToken token = tokenSource.Token;
- // create the task
- Task task = new Task(() =>
- {
- for (int i = 0; i < int.MaxValue; i++)
- {
- if (token.IsCancellationRequested)
- {
- Console.WriteLine("Task cancel detected");
- throw new OperationCanceledException(token);
- }
- else
- {
- Console.WriteLine("Int value {0}", i);
- }
- }
- }, token);
- // register a cancellation delegate
- token.Register(() =>
- {
- Console.WriteLine(">>>>>> Delegate Invoked\n");
- });
- // wait for input before we start the task
- Console.WriteLine("Press enter to start task");
- Console.WriteLine("Press enter again to cancel task");
- Console.ReadLine();
- // start the task
- task.Start();
- // read a line from the console.
- Console.ReadLine();
- // cancel the task
- Console.WriteLine("Cancelling task");
- tokenSource.Cancel();
- // wait for input before exiting
- Console.WriteLine("Main method complete. Press enter to finish.");
- Console.ReadLine();
- }
3. 用Wait Handle還檢測Task是否被取消
第三種方法檢測task是否被cancel就是調(diào)用CancellationToken.WaitHandle屬性。對于這個屬性的詳細使用,在后續(xù)的文章中會深入的講述,在這里主要知道一點就行了:CancellationToken的WaitOne()方法會阻止task的運行,只有CancellationToken的cancel()方法被調(diào)用后,這種阻止才會釋放。
在下面的例子中,創(chuàng)建了兩個task,其中task2調(diào)用了WaitOne()方法,所以task2一直不會運行,除非調(diào)用了CancellationToken的Cancel()方法,所以WaitOne()方法也算是檢測task是否被cancel的一種方法了。
代碼
- static void Main(string[] args)
- {
- // create the cancellation token source
- CancellationTokenSource tokenSource = new CancellationTokenSource();
- // create the cancellation token
- CancellationToken token = tokenSource.Token;
- // create the task
- Task task1 = new Task(() =>
- {
- for (int i = 0; i < int.MaxValue; i++)
- {
- if (token.IsCancellationRequested)
- {
- Console.WriteLine("Task cancel detected");
- throw new OperationCanceledException(token);
- }
- else
- {
- Console.WriteLine("Int value {0}", i);
- }
- }
- }, token);
- // create a second task that will use the wait handle
- Task task2 = new Task(() =>
- {
- // wait on the handle
- token.WaitHandle.WaitOne();
- // write out a message
- Console.WriteLine(">>>>> Wait handle released");
- });
- // wait for input before we start the task
- Console.WriteLine("Press enter to start task");
- Console.WriteLine("Press enter again to cancel task");
- Console.ReadLine();
- // start the tasks
- task1.Start();
- task2.Start();
- // read a line from the console.
- Console.ReadLine();
- // cancel the task
- Console.WriteLine("Cancelling task");
- tokenSource.Cancel();
- // wait for input before exiting
- Console.WriteLine("Main method complete. Press enter to finish.");
- Console.ReadLine();
- }
4. 取消多個Task
我們可以使用一個CancellationToken來創(chuàng)建多個不同的Tasks,當(dāng)這個CancellationToken的Cancel()方法調(diào)用的時候,使用了這個token的多個task都會被取消。
代碼
- static void Main(string[] args)
- {
- // create the cancellation token source
- CancellationTokenSource tokenSource = new CancellationTokenSource();
- // create the cancellation token
- CancellationToken token = tokenSource.Token;
- // create the tasks
- Task task1 = new Task(() =>
- {
- for (int i = 0; i < int.MaxValue; i++)
- {
- token.ThrowIfCancellationRequested();
- Console.WriteLine("Task 1 - Int value {0}", i);
- }
- }, token);
- Task task2 = new Task(() =>
- {
- for (int i = 0; i < int.MaxValue; i++)
- {
- token.ThrowIfCancellationRequested();
- Console.WriteLine("Task 2 - Int value {0}", i);
- }
- }, token);
- // wait for input before we start the tasks
- Console.WriteLine("Press enter to start tasks");
- Console.WriteLine("Press enter again to cancel tasks");
- Console.ReadLine();
- // start the tasks
- task1.Start();
- task2.Start();
- // read a line from the console.
- Console.ReadLine();
- // cancel the task
- Console.WriteLine("Cancelling tasks");
- tokenSource.Cancel();
- // wait for input before exiting
- Console.WriteLine("Main method complete. Press enter to finish.");
- Console.ReadLine();
- }
5. 創(chuàng)建組合的取消Task的Token
我們可以用CancellationTokenSource.CreateLinkedTokenSource()方法來創(chuàng)建一個組合的token,這個組合的token有很多的CancellationToken組成。主要組合token中的任意一個token調(diào)用了Cancel()方法,那么使用這個組合token的所有task就會被取消。代碼如下:
代碼
- static void Main(string[] args)
- {
- // create the cancellation token sources
- CancellationTokenSource tokenSource1 = new CancellationTokenSource();
- CancellationTokenSource tokenSource2 = new CancellationTokenSource();
- CancellationTokenSource tokenSource3 = new CancellationTokenSource();
- // create a composite token source using multiple tokens
- CancellationTokenSource compositeSource =
- CancellationTokenSource.CreateLinkedTokenSource(
- tokenSource1.Token, tokenSource2.Token, tokenSource3.Token);
- // create a cancellable task using the composite token
- Task task = new Task(() =>
- {
- // wait until the token has been cancelled
- compositeSource.Token.WaitHandle.WaitOne();
- // throw a cancellation exception
- throw new OperationCanceledException(compositeSource.Token);
- }, compositeSource.Token);
- // start the task
- task.Start();
- // cancel one of the original tokens
- tokenSource2.Cancel();
- // wait for input before exiting
- Console.WriteLine("Main method complete. Press enter to finish.");
- Console.ReadLine();
- }
6. 判斷一個Task是否已經(jīng)被取消了
可以使用Task的IsCancelled屬性來判斷task是否被取消了。代碼如下:
代碼
- static void Main(string[] args)
- {
- // create the cancellation token source
- CancellationTokenSource tokenSource1 = new CancellationTokenSource();
- // create the cancellation token
- CancellationToken token1 = tokenSource1.Token;
- // create the first task, which we will let run fully
- Task task1 = new Task(() =>
- {
- for (int i = 0; i < 10; i++)
- {
- token1.ThrowIfCancellationRequested();
- Console.WriteLine("Task 1 - Int value {0}", i);
- }
- }, token1);
- // create the second cancellation token source
- CancellationTokenSource tokenSource2 = new CancellationTokenSource();
- // create the cancellation token
- CancellationToken token2 = tokenSource2.Token;
- // create the second task, which we will cancel
- Task task2 = new Task(() =>
- {
- for (int i = 0; i < int.MaxValue; i++)
- {
- token2.ThrowIfCancellationRequested();
- Console.WriteLine("Task 2 - Int value {0}", i);
- }
- }, token2);
- // start all of the tasks
- task1.Start();
- task2.Start();
- // cancel the second token source
- tokenSource2.Cancel();
- // write out the cancellation detail of each task
- Console.WriteLine("Task 1 cancelled? {0}", task1.IsCanceled);
- Console.WriteLine("Task 2 cancelled? {0}", task2.IsCanceled);
- // wait for input before exiting
- Console.WriteLine("Main method complete. Press enter to finish.");
- Console.ReadLine();
- }
原文標(biāo)題:.NET 4 并行(多核)編程系列之三
鏈接:http://www.cnblogs.com/Leo_wl/archive/2010/06/01/1749596.html
【編輯推薦】