C# Taskの引数に使うCancellationTokenは何に使われているのか?
はじめに
Taskのキャンセル周りの実装で、ふと思いました。
CancellationTokenをTask開始のメソッドで指定できるけど、
ラムダの外で定義したtokenを使うんだから指定しなくてもよくない??
何か理由があるのだろうか??
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task.Factory.StartNew(() =>
{
/* 非同期の処理 */
if (token.IsCancellationRequested) { return; }
}, token/*<-こいつな!!*/);
指定する理由
色々探していたら同じ疑問を見つけました。
https://stackoverflow.com/questions/3712939/cancellation-token-in-task-constructor-why
- タスク開始前にトークンがキャンセルされてるかのチェック。タスクを開始するコストが減るから。
- タスクのボディーでも監視をしていて、例外を投げたトークンとボディーのトークンが一致するかを見ていて、同じだったら、タスクのステータスはCanceledになる。普通は、Faulted。
なるほど、指定すると少し挙動が変わったり、タスクのステータスが細かく設定されるところみたいですね。
と言うわけで、ちょいと検証してみましょう!!
検証
検証パターンと結果を書きます。ソースはいっぱいになるので、最後に貼り付けておきます。
VS2017で検証
パターン1.Task.Factory.StartNew()を使用する
パターン2.Task.Run()を使用する
A.正常にTaskを終了させる
B.Task実行前にキャンセルを行う
C.引数のCancellationTokenとTask内のCancellationTokenを同一のものを使用し、実行中にCancelを行う
D.引数のCancellationTokenとTask内のCancellationTokenを別のものにして、実行中にCancelを行う
1.Task.Factory.StartNew() | 2.Task.Run() | |
A.正常にTaskを終了させる | Task実行後+RanToComplation | Task実行後+RanToComplation |
B.Task実行前にキャンセルを行う | Task実行せずCanceld | Task実行せずCanceld |
C.引数のCancellationTokenとTask内のCancellationTokenを同一のものを使用し、実行中にCancelを行う | 例外を発生させCanceld | 例外を発生させCanceld |
D.引数のCancellationTokenとTask内のCancellationTokenを別のものにして、実行中にCancelを行う | 例外発生しFaulted | 例外を発生させCanceld |
Task.Factory.StartNew()は言われていた結果通りになりました。
しかし、Task.Run()はタスクのステータスがFaultedに変わりませんでした。
ステータスを見るときには少し注意が必要ですね。
※て言うか、Dみたいな意味の分からない使い方はしないだろうし、メンバで持ってるCancellationTokenSourceのインスタンスが変わってしまうのも設計が悪そうだし…
TaskをCancelしてわかったこと
その他、キャンセルした時の挙動で分かったことも書いておきます。
キャンセルされたタスクのWaitを行うと例外(TaskCanceledException: A task was canceled.)が発生する。
そのため、キャンセルを前提としている場合は例外処理が必要。
キャンセルの反映が遅いので、DelayをかけないとWaitingToRunになってしまうことがあった。
処理にステータス判定をしている時はタイムラグに注意。
最後に
最初にまとまってたんですけど、今回はじっくり検証したのでブログにしました。
疑問に思った方の解決になれば幸いです。
そして、最後にテストコードを貼ります。
テストコード
// テストコードの枠---開始---
using System;
using System.Threading;
using System.Threading.Tasks;
namespace task
{
class Program
{
// 一個ずつ入れる
}
}
// テストコードの枠---終了---
// 1-A
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("実行したよ");
}, token);
t.Wait();
Console.WriteLine(t.Status.ToString());
Console.ReadKey();
}
// 1-B
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
tokenSource.Cancel();
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("実行したよ");
}, token);
//t.Wait();
Task.Delay(2000);
Console.WriteLine(t.Status.ToString());
Console.ReadKey();
}
// 1-C
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task t = Task.Factory.StartNew(() =>
{
while (true)
{
token.ThrowIfCancellationRequested();
}
}, token).ContinueWith((_t) =>
{
Console.WriteLine(_t.Status.ToString());
});
Task.Delay(1000);
tokenSource.Cancel();
Console.ReadKey();
}
// 1-D
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
CancellationTokenSource tokenSource2 = new CancellationTokenSource();
CancellationToken token2 = tokenSource2.Token;
Task t = Task.Factory.StartNew(() =>
{
while (true)
{
token.ThrowIfCancellationRequested();
}
}, token2).ContinueWith((_t) =>
{
Console.WriteLine(_t.Status.ToString());
});
Task.Delay(1000);
tokenSource.Cancel();
Console.ReadKey();
}
// 2-A
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task t = Task.Run(() =>
{
Console.WriteLine("実行したよ");
}, token);
t.Wait();
Console.WriteLine(t.Status.ToString());
Console.ReadKey();
}
// 2-B
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
tokenSource.Cancel();
Task t = Task.Run(() =>
{
Console.WriteLine("実行したよ");
}, token);
//t.Wait();
Task.Delay(2000);
Console.WriteLine(t.Status.ToString());
Console.ReadKey();
}
// 2-C
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task t = Task.Run(() =>
{
while (true)
{
token.ThrowIfCancellationRequested();
}
}, token).ContinueWith((_t) =>
{
Console.WriteLine(_t.Status.ToString());
});
Task.Delay(1000);
tokenSource.Cancel();
Console.ReadKey();
}
// 2-D
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
CancellationTokenSource tokenSource2 = new CancellationTokenSource();
CancellationToken token2 = tokenSource2.Token;
Task t = Task.Run(() =>
{
while (true)
{
token.ThrowIfCancellationRequested();
}
}, token2).ContinueWith((_t) =>
{
Console.WriteLine(_t.Status.ToString());
});
Task.Delay(1000);
tokenSource.Cancel();
Console.ReadKey();
}