非同期を扱うメソッドで、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 を学んでみたいと思います。