TPL入門 (13) - タスクのキャンセル

posted by xin9le on

No comments

今回はタスクをキャンセルする方法について見ていきます。以前ループの取り消しでも触れましたが、.NET Framework 4ではキャンセルトークンを用いた統一的なキャンセル手法が提供されています。そして、タスクもそれを使ってキャンセルできるようにサポートされています。

タスクの実行を取り消す

まず、次のサンプルを見てください。

Task Cancel Sample (1)
  1. using System;
  2. using System.Threading;
  3. using System.Threading.Tasks;
  4.  
  5. namespace ConsoleApplication
  6. {
  7.     class Program
  8.     {
  9.         static void Main()
  10.         {
  11.             var source  = new CancellationTokenSource();
  12.             var task    = new Task(() => Console.WriteLine("タスクは実行されました。"), source.Token);
  13.             task.Start();
  14.             source.Cancel();
  15.             try
  16.             {
  17.                 task.Wait();
  18.             }
  19.             catch (AggregateException exception)
  20.             {
  21.                 foreach (var inner in exception.InnerExceptions)
  22.                 {
  23.                     Console.WriteLine(inner.Message);
  24.                     Console.WriteLine("Type : {0}", inner.GetType());
  25.                 }
  26.             }
  27.         }
  28.     }
  29. }
  30.  
  31. //----- 結果
  32. /*
  33. タスクが取り消されました。
  34. Type : System.Threading.Tasks.TaskCanceledException
  35. */

上記のサンプルでは、タスクを開始するように指示していますが、結果としてタスクは実行されず、キャンセルされています。一体何が起こっているのでしょうか?ポイントは、CancellationTokenSource.CancelメソッドTask.Startメソッドの直後に呼び出しているところです。内部では大体次のようになっているはずです。
  1. タスクとCancellationTokenをコンストラクタで関連付ける
  2. Startメソッドの呼び出しにより、既定のタスクスケジューラーにタスクを登録する
  3. タスクスケジューラーがタスクを開始する前にCancelメソッドが実行される
  4. タスクスケジューラーはタスクに関連付いているキャンセルトークンを確認する
  5. キャンセルが要求されていることが確認できたため、タスクの開始を取り消す
  6. タスクが保持する例外にTaskCanceledExceptionを登録する
  7. Waitメソッド呼び出し時、タスクが例外を保持しているのでスローする
タスクのキャンセルがタスクの実行とは非同期に行われるため、このようなことが起こります。試しに、StartメソッドとCancelメソッドの間で時間が経過するようにThread.Sleepなどを入れてみると、タスクがキャンセルされずに実行されることがわかります。


実行途中でのキャンセル

タスクの実行中にキャンセルする方法も紹介します。こちらの方法はループの取り消しで説明した内容とほぼ同様です。タスク実行中にタスクがキャンセルされたかどうかをポーリングで監視し、キャンセルが要求されたらOperationCanceledExceptionをスローします。タスクの実行自体が取り消されたわけではないので、先程とは例外の型が異なることに注意してください。

Task Cancel Sample (2)
  1. using System;
  2. using System.Linq;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5.  
  6. namespace ConsoleApplication
  7. {
  8.     class Program
  9.     {
  10.         static void Main()
  11.         {
  12.             var source  = new CancellationTokenSource();
  13.             var token   = source.Token;
  14.             var task    = new Task(() =>
  15.             {
  16.                 Console.WriteLine("Task : Begin");
  17.                 foreach (var item in Enumerable.Range(0, 10))
  18.                 {
  19.                     token.ThrowIfCancellationRequested();
  20.                     Thread.Sleep(100);
  21.                 }
  22.                 Console.WriteLine("Task : End");
  23.             });
  24.             task.Start();
  25.             Thread.Sleep(300);    //--- タスクが実行されるようにする
  26.             source.Cancel();
  27.             try
  28.             {
  29.                 task.Wait();
  30.             }
  31.             catch (AggregateException exception)
  32.             {
  33.                 foreach (var inner in exception.InnerExceptions)
  34.                 {
  35.                     Console.WriteLine(inner.Message);
  36.                     Console.WriteLine("Type : {0}", inner.GetType());
  37.                 }
  38.             }
  39.         }
  40.     }
  41. }
  42.  
  43. //----- 結果
  44. /*
  45. Task : Begin
  46. 操作はキャンセルされました。
  47. Type : System.OperationCanceledException
  48. */


次回予告

今回はタスクをキャンセルする方法について見てきました。次回は、実行中や実行前後のタスクの状態を見てみたいと思います。

Leave a Reply