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

  1. タスク開始前にトークンがキャンセルされてるかのチェック。タスクを開始するコストが減るから。
  2. タスクのボディーでも監視をしていて、例外を投げたトークンとボディーのトークンが一致するかを見ていて、同じだったら、タスクのステータスは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();
}

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です