Cancellation Token について調べてみる

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

Program.cs

```csharp

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

```csharp
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 を呼び出す非同期メソッドに渡してやればいい感じです。

```csharp
        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 文で何かの処理をするなり、例外を発生させるなりで、キャンセル処理に対応できるようになっています。

```csharp

        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 ミリ秒毎に再び動作するような作りになっていますので、３回ループが回ってそのあとに、Cancel されるので、４回目の現在時が表示されたあとに、キャンセルの例外が発生します。

# つまったところ

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

Program.cs

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

CancelSample.cs

```csharp

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

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

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