読者です 読者をやめる 読者になる 読者になる

xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

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

今回はタスクをキャンセルする方法について見ていきます。以前ループの取り消しでも触れましたが、.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メソッドの直後に呼び出しているところです。内部では大体次のようになっているはずです。

  1. タスクとCancellationTokenコンストラクタで関連付ける
  2. Startメソッドの呼び出しにより、既定のタスクスケジューラーにタスクを登録する
  3. タスクスケジューラーがタスクを開始する前にCancelメソッドが実行される
  4. タスクスケジューラーはタスクに関連付いているキャンセルトークンを確認する
  5. キャンセルが要求されていることが確認できたため、タスクの開始を取り消す
  6. タスクが保持する例外にTaskCanceledExceptionを登録する
  7. 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
*/

次回予告

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