C#のタスクキャンセル

C#でスレッドプールからタスクを実行するとき、
タスクを途中でキャンセルしたい場面も出てくるでしょう。

スレッドプールからタスクを実行するにはTaskFactory.StartNew()を用います。

TaskFactory.StartNew()メソッドによりタスクを実行するとき、
タスクを途中でキャンセルできるようにするには
第2引数にCancellationTokenを指定します。

これはCancellationTokenSource.Tokenフィールドとなります。

タスクにキャンセル要求を出すにはCancellationTokenSource.Cancel()メソッドを実行します。
これによりキャンセルフラグが立ちます。

タスク側では特定のタイミングでこのキャンセルフラグをチェックする必要があります。
キャンセルフラグはCancellationToken.IsCancellationRequestedフィールドです。
このフラグが立ったらタスク側はOperationCanceledException例外をスローするようにします。
したがって、フラグチェックから例外スローのコードは以下のようになるでしょう。

    if (token.IsCancellationRequested)
        throw new OperationCanceledException(token);

しかし、これをいちいち随所に書くのは手間なので、
以下のように一文で済ませてくれるメソッドが用意されています。

    token.ThrowIfCancellationRequested();

これをタスク内の特定の箇所に埋め込んでおきます。

タスクの呼び出し元ではキャンセル要求をした後にキャンセルされるまで待機する必要があります。
待機はTask.Wait()メソッドにより行います。
タスクがキャンセルされると呼び出し元スレッドで
AggregateException例外が発生するため、これをキャッチします。

タスクキャンセルを使ったコードは以下のようになります。

    class TaskCancel
    {
        public void RunTask() {
            TaskFactory taskFactory = new TaskFactory();
            CancellationTokenSource tokenSource = new CancellationTokenSource();

            Task task = taskFactory.StartNew(() => {
                int count = 0;

                while (true)
                {
                    // キャンセル要求がきていたらOperationCanceledException例外をスロー
                    tokenSource.Token.ThrowIfCancellationRequested();

                    // カウント値を出力する
                    Console.WriteLine(count++);
                    Thread.Sleep(500);
                }

            }, tokenSource.Token);

            // キー入力待機
            Console.ReadLine();

            try
            {
                // キャンセル要求出す
                tokenSource.Cancel();

                // タスクがキャンセルされるまで待機
                task.Wait();
            }
            catch (AggregateException)
            {
                // タスクがキャンセルされるとここが実行される
                Console.WriteLine("Task is cancelled.");

            }

            Console.ReadLine();
        }
    }

スレッドプールよりタスクを起動すると
タスクは0.5秒ごとにカウント値を出力します。

ここでユーザが文字列を入力するとキャンセル要求がタスク側に発行され
タスクはこれをチェックして終了します。

異なるスレッドのタスクを強制終了するのは大変危険ですが
このようなフラグチェックの仕組みを用いれば安全です。
C#に限らず他のプログラミング言語でも同様のことが言えるでしょう。

■参考サイト
タスクのキャンセル