タスクコンストラクターのキャンセルトークン:なぜですか?


223

特定のSystem.Threading.Tasks.TaskコンストラクターCancellationTokenはパラメーターとしてを受け取ります。

CancellationTokenSource source = new CancellationTokenSource();
Task t = new Task (/* method */, source.Token);

これについて私を困惑させるのは、メソッド本体の内部から、渡されたトークンを実際に取得する方法がないということです(たとえば、のようなものはありませんTask.CurrentTask.CancellationToken)。トークンは、状態オブジェクトなどの他のメカニズムを通じて提供されるか、ラムダにキャプチャされる必要があります。

それでは、コンストラクタでキャンセルトークンを提供することにはどのような目的がありますか?

回答:


254

a CancellationTokenTaskコンストラクタに渡すと、タスクに関連付けられます。

MSDNからのStephen Toubの回答を引用:

これには2つの主要な利点があります。

  1. トークンのTask実行開始前にキャンセルが要求されている場合、Taskは実行されません。に移行するのRunningではなく、 すぐにに移行しCanceledます。これにより、実行中にキャンセルされた場合にタスクを実行するコストを回避できます。
  2. タスクの本文もキャンセルトークンを監視していて、OperationCanceledExceptionそのトークンを含む(これが何をするかThrowIfCancellationRequested)をスローしている場合、タスクはOperationCanceledExceptionそれを検出すると、OperationCanceledExceptionのトークンがタスクのトークンと一致するかどうかを確認します。存在する場合、その例外は、協調的なキャンセルの確認Taskと、Canceled 状態ではなく状態への遷移と見なされFaultedます。

2
TPLは非常によく考えられています。
大佐パニック

1
私は利益1にトークン取り消しを渡すと同様に適用されると仮定Parallel.ForまたはParallel.ForEach
大佐パニック

27

コンストラクターは、内部でキャンセル処理にトークンを使用します。コードでトークンへのアクセスが必要な場合は、トークンを自分に渡す必要があります。CodePlexのMicrosoft .NETによる並列プログラミングを読むことを強くお勧めします

本のCTSの使用例:

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task myTask = Task.Factory.StartNew(() =>
{
    for (...)
    {
        token.ThrowIfCancellationRequested();

        // Body of for loop.
    }
}, token);

// ... elsewhere ...
cts.Cancel();

3
トークンをパラメーターとして渡さないとどうなりますか?動作は同じで、目的はないようです。
sergtk

2
@sergdev:トークンを渡して、タスクとスケジューラに登録します。渡さずに使用すると、未定義の動作になります。
user7116

3
@sergdev:テスト後:トークンをパラメーターとして渡さないと、myTask.IsCanceledとmyTask.Statusは同じになりません。ステータスはキャンセルされずに失敗します。それでも例外は同じです。どちらの場合も、それはOperationCanceledExceptionです。
Olivier de Rivoyre 2015

2
電話しないとtoken.ThrowIfCancellationRequested();どうなりますか?私のテストでは、動作は同じです。何か案は?
マチナリウム

1
@CobaltBlue:when cts.Cancel() is called the Task is going to get canceled and end, no matter what you doいいえ。それが開始された前のタスクがキャンセルされた場合、それがされてキャンセル。タスクの本体がトークンをチェックしないだけの場合、タスクは最後まで実行され、RanToCompletionステータスになります。本文がOperationCancelledExceptionによってスローされた場合、ThrowIfCancellationRequestedタスクは、その例外のCancellationTokenがタスクに関連付けられているものと同じかどうかを確認します。そうであれば、タスクはキャンセルされます。そうでない場合は、Faultedです。
Wolfzoon、2016年

7

キャンセルは、多くの人が考えているような単純なケースではありません。msdnに関するこのブログ投稿では、いくつかの微妙な点について説明しています。

例えば:

Parallel Extensionsや他のシステムの特定の状況では、ユーザーによる明示的なキャンセルが原因ではない理由により、ブロックされたメソッドをウェイクアップする必要があります。たとえばblockingCollection.Take()、コレクションが空であるために1つのスレッドがブロックされblockingCollection.CompleteAdding()、その後別のスレッドがを呼び出す 場合、最初の呼び出しが起動し、をスローしInvalidOperationExceptionて、誤った使用法を表す必要があります。

Parallel Extensionsでのキャンセル


4

Max Galkinによる回答の 2つのポイントを示す例を次に示します

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(true);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(true);
        Console.WriteLine();

        Console.WriteLine();
        Console.WriteLine("Test Done!!!");
        Console.ReadKey();
    }

    static void StartCanceledTaskTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, false), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, false));
        }

        Console.WriteLine("Canceling task");
        tokenSource.Cancel();

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void ThrowIfCancellationRequestedTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, true), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, true));
        }

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            Thread.Sleep(100);

            Console.WriteLine("Canceling task");
            tokenSource.Cancel();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void TaskWork(CancellationToken token, bool throwException)
    {
        int loopCount = 0;

        while (true)
        {
            loopCount++;
            Console.WriteLine("Task: loop count {0}", loopCount);

            token.WaitHandle.WaitOne(50);
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Task: cancellation requested");
                if (throwException)
                {
                    token.ThrowIfCancellationRequested();
                }

                break;
            }
        }
    }
}

出力:

*********************************************************************
* Start canceled task, don't pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Task: loop count 1
Task: cancellation requested
Task.Status: RanToCompletion

*********************************************************************
* Start canceled task, pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Exception: Start may not be called on a task that has completed.
Task.Status: Canceled

*********************************************************************
* Throw if cancellation requested, don't pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: The operation was canceled.
Task.Status: Faulted

*********************************************************************
* Throw if cancellation requested, pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: A task was canceled.
Task.Status: Canceled


Test Done!!!
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.