46
35

More than 5 years have passed since last update.

Cancellation Token について調べてみる

Last updated at Posted at 2017-07-31

非同期を扱うメソッドで、CancellationToken というのが時々登場しますが、なんとなくしかわかっていなかったので、ちょっとサンプル作って試してみました。とっても簡単でした。

Program.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CancellationSample
{
    class Program
    {
        static void Main(string[] args)
        {
            new CancelSample().ExecAsync();
            Console.ReadLine();
        }
    }
}

CancelSample

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;

namespace CancellationSample
{
    public class CancelSample
    {


        private async Task EternalLoopAsync(CancellationToken ct)
        {
            while (true)
            {
                ct.ThrowIfCancellationRequested();
               // if (ct.IsCancellationRequested) { return; }
                await Task.Delay(6000);
                Console.WriteLine($"Eternal Loop: {DateTime.Now}");

            }
        }

        public async Task ExecAsync()
        {
            try
            {
                await TimerEventAsync();
            } catch (OperationCanceledException ce)
            {
                Console.WriteLine($"Canceled!: {ce}");
            }
        }

        private CancellationTokenSource cts;

        private async Task TimerEventAsync()
        {
            cts = new CancellationTokenSource();
            var timer = new System.Timers.Timer();
            timer.Interval = 19000;
            timer.Enabled = true;
            timer.Elapsed += new ElapsedEventHandler(CancelHappens);
            await EternalLoopAsync(cts.Token);

        }

        private void CancelHappens(object source, ElapsedEventArgs e)
        {
            Console.WriteLine("Cancel happens!");
            cts.Cancel();
        }

    }
}

Cancellation Token とは

Cancellation Token は、非同期処理を実施するときに、非同期処理をキャンセルするための仕組みです。非同期のルーチンを実行した後で、どうやってその処理を取り消せばいいでしょう?そういう時に使います。

使い方はとっても簡単です。呼び出し側に、CancellationTokenSource クラスのインスタンスを定義して、そのToken を呼び出す非同期メソッドに渡してやればいい感じです。

        private CancellationTokenSource cts;
            cts = new CancellationTokenSource();
            var timer = new System.Timers.Timer();
            timer.Interval = 19000;
            timer.Enabled = true;
            timer.Elapsed += new ElapsedEventHandler(CancelHappens);
            await EternalLoopAsync(cts.Token);

        }

        private void CancelHappens(object source, ElapsedEventArgs e)
        {
            Console.WriteLine("Cancel happens!");
            cts.Cancel();
        }
                   : 

ここでは、19000ミリ秒後に、イベントハンドラが発火して、CancelHappens メソッドが起動されて、cts.Cancel() で、キャンセルが発行されます。

呼び出されたほうはどうなっているか?というと、Cancel が発行されると、if 文で何かの処理をするなり、例外を発生させるなりで、キャンセル処理に対応できるようになっています。


        private async Task EternalLoopAsync(CancellationToken ct)
        {
            while (true)
            {
                ct.ThrowIfCancellationRequested();
               // if (ct.IsCancellationRequested) { return; }
                await Task.Delay(6000);
                Console.WriteLine($"Eternal Loop: {DateTime.Now}");

            }
        }

ct.ThroIfCanllationRequested() が例外がスローされるパターンで、 ct.IsCancellationRequested が Boolean の値を検査して、自分でキャンセル処理を行うものです。今は例外モードになっていますが、実行するとこんな感じです。予想通り。

Eternal Loop: 2017/07/30 21:25:56
Eternal Loop: 2017/07/30 21:26:02
Eternal Loop: 2017/07/30 21:26:08
Cancel happens!
Eternal Loop: 2017/07/30 21:26:14
Canceled!: System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowOperationCanceledException()
   at System.Threading.CancellationToken.ThrowIfCancellationRequested()
   at CancellationSample.CancelSample.<EternalLoopAsync>d__0.MoveNext() in c:\users\tsushi\Source\Repos\CancellationSample\CancellationSample\CancelSample.cs:line 19
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at CancellationSample.CancelSample.<TimerEventAsync>d__3.MoveNext() in c:\users\tsushi\Source\Repos\CancellationSample\CancellationSample\CancelSample.cs:line 47
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at CancellationSample.CancelSample.<ExecAsync>d__1.MoveNext() in c:\users\tsushi\Source\Repos\CancellationSample\CancellationSample\CancelSample.cs:line 31

キャンセルが起こったら例外が発生する感じです。呼び出される側のプログラムは、6000 ミリ秒毎に再び動作するような作りになっていますので、3回ループが回ってそのあとに、Cancel されるので、4回目の現在時が表示されたあとに、キャンセルの例外が発生します。

つまったところ

考え方自体は迷わなかったのですが、当初は例外が起こらないように見えて「あれー」と思いました。
当初はこういうコードでした。

Program.cs

    class Program
    {
        static void Main(string[] args)
        {
            new CancelSample().Exec();
            Console.ReadLine();
        }
    }

CancelSample.cs


 :
        public void Exec()
        {
                TimerEventAsync();
        }

これだと、きっと、非同期メソッドを実行して、このExec をさっと抜けているので、例外の表示され先がないので、何も起こらないようにみえるという感じだと思います。先のソースのように、ここで、await と、例外のキャッチを入れておけば、例外が投げられているのがわかります。

以上です。次は Linq を学んでみたいと思います。

46
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
35