今回はタスクをキャンセルする方法について見ていきます。以前ループの取り消しでも触れましたが、.NET Framework 4ではキャンセルトークンを用いた統一的なキャンセル手法が提供されています。そして、タスクもそれを使ってキャンセルできるようにサポートされています。
タスクの実行を取り消す
まず、次のサンプルを見てください。
using System; using System.Threading; using System.Threading.Tasks; namespace Sample26_CancelTaskStart { class Program { static void Main() { var source = new CancellationTokenSource(); var task = new Task(() => Console.WriteLine("タスクは実行されました。"), source.Token); task.Start(); source.Cancel(); try { task.Wait(); } catch (AggregateException exception) { foreach (var inner in exception.InnerExceptions) { Console.WriteLine(inner.Message); Console.WriteLine("Type : {0}", inner.GetType()); } } } } } //----- 結果 /* タスクが取り消されました。 Type : System.Threading.Tasks.TaskCanceledException */
上記のサンプルでは、タスクを開始するように指示していますが、結果としてタスクは実行されず、キャンセルされています。一体何が起こっているのでしょうか?ポイントは、CancellationTokenSource.CancelメソッドをTask.Startメソッドの直後に呼び出しているところです。内部では大体次のようになっているはずです。
- タスクとCancellationTokenをコンストラクタで関連付ける
- Startメソッドの呼び出しにより、既定のタスクスケジューラーにタスクを登録する
- タスクスケジューラーがタスクを開始する前にCancelメソッドが実行される
- タスクスケジューラーはタスクに関連付いているキャンセルトークンを確認する
- キャンセルが要求されていることが確認できたため、タスクの開始を取り消す
- タスクが保持する例外にTaskCanceledExceptionを登録する
- Waitメソッド呼び出し時、タスクが例外を保持しているのでスローする
タスクのキャンセルがタスクの実行とは非同期に行われるため、このようなことが起こります。試しに、StartメソッドとCancelメソッドの間で時間が経過するようにThread.Sleepなどを入れてみると、タスクがキャンセルされずに実行されることがわかります。
実行途中でのキャンセル
タスクの実行中にキャンセルする方法も紹介します。こちらの方法はループの取り消しで説明した内容とほぼ同様です。タスク実行中にタスクがキャンセルされたかどうかをポーリングで監視し、キャンセルが要求されたらOperationCanceledExceptionをスローします。タスクの実行自体が取り消されたわけではないので、先程とは例外の型が異なることに注意してください。
using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Sample27_CancelTaskByPolling { class Program { static void Main() { var source = new CancellationTokenSource(); var token = source.Token; var task = new Task(() => { Console.WriteLine("Task : Begin"); foreach (var item in Enumerable.Range(0, 10)) { token.ThrowIfCancellationRequested(); Thread.Sleep(100); } Console.WriteLine("Task : End"); }); task.Start(); Thread.Sleep(300); //--- タスクが実行されるようにする source.Cancel(); try { task.Wait(); } catch (AggregateException exception) { foreach (var inner in exception.InnerExceptions) { Console.WriteLine(inner.Message); Console.WriteLine("Type : {0}", inner.GetType()); } } } } } //----- 結果 /* Task : Begin 操作はキャンセルされました。 Type : System.OperationCanceledException */
次回予告
今回はタスクをキャンセルする方法について見てきました。次回は、実行中や実行前後のタスクの状態を見てみたいと思います。