web-dev-qa-db-ja.com

Task.RunでタイムアウトにCancellationTokenを使用しても機能しない

OK、私の質問は本当に簡単です。このコードがTaskCancelledExceptionをスローしないのはなぜですか?

static void Main()
{
    var v = Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }, new CancellationTokenSource(500).Token).Result;

    Console.WriteLine(v); // this outputs 10 - instead of throwing error.
    Console.Read();
}

しかし、これは動作します

static void Main()
{
    var v = Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }, new CancellationToken(true).Token).Result;

    Console.WriteLine(v); // this one throws
    Console.Read();
}
29
Aliostad

マネージスレッドでのキャンセル

キャンセルは協調的であり、リスナーには強制されません。リスナーは、キャンセル要求への応答で正常に終了する方法を決定します。

Task.Runメソッド内にCancellationTokenにアクセスしてキャンセルを実装するコードを記述しなかったため、キャンセルのリクエストを事実上無視して完了まで実行しました。

33

実行中のタスクと実行予定のタスクのキャンセルには違いがあります。

Task.Runメソッドの呼び出し後、タスクはスケジュールされているだけであり、おそらくまだ実行されていない可能性があります。

キャンセルをサポートするオーバーロードのTask.Run(...、CancellationToken)ファミリを使用すると、タスクが実行される直前にキャンセルトークンがチェックされます。この時点でキャンセルトークンのIsCancellationRequestedがtrueに設定されている場合、タイプTaskCanceledExceptionの例外がスローされます。

タスクが既に実行されている場合、ThrowIfCancellationRequestedメソッドを呼び出すか、OperationCanceledExceptionをスローするのはタスクの責任です。

MSDNによると、これは次の便利な方法にすぎません。

if(token.IsCancellationRequested)throw new OperationCanceledException(token);

この2つのケースで使用される例外の種類は異なります。

catch (TaskCanceledException ex)
{
    // Task was canceled before running.
}
catch (OperationCanceledException ex)
{
    // Task was canceled while running.
}

TaskCanceledExceptionOperationCanceledExceptionから派生しているため、catchタイプに対してOperationCanceledException句を1つだけ持つことができます。

catch (OperationCanceledException ex)
{
    if (ex is TaskCanceledException)
        // Task was canceled before running.
    // Task was canceled while running.
}
20
George Polevoy

CancellationTokenオブジェクトからメソッド ThrowIfCancellationRequested() を呼び出していないためだと思います。このようにして、タスクのキャンセルのリクエストを無視しています。

次のようにする必要があります。

void Main()
{
    var ct = new CancellationTokenSource(500).Token;
     var v = 
     Task.Run(() =>
    {
        Thread.Sleep(1000);
        ct.ThrowIfCancellationRequested();
        return 10;
    }, ct).Result;

    Console.WriteLine(v); //now a TaskCanceledException is thrown.
    Console.Read();
}

コードの2番目のバリアントは、Canceled状態がtrueに設定されたトークンを既に初期化しているため、機能します。確かに、述べられているように ここ

If canceled is true, both CanBeCanceled and IsCancellationRequested will be true

キャンセルは既に要求されているため、実際にタスクを開始することなく、例外TaskCanceledExceptionがすぐにスローされます。

18
Alberto Solano

代わりにToken.Sleepを使用してTask.Delayを使用する別の実装。

 static void Main(string[] args)
    {
        var task = GetValueWithTimeout(1000);
        Console.WriteLine(task.Result);
        Console.ReadLine();
    }

    static async Task<int> GetValueWithTimeout(int milliseconds)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;
        cts.CancelAfter(milliseconds);
        token.ThrowIfCancellationRequested();

        var workerTask = Task.Run(async () =>
        {
            await Task.Delay(3500, token);
            return 10;
        }, token);

        try
        {
            return await workerTask;
        }
        catch (OperationCanceledException )
        {
            return 0;
        }
    }
1
Z.R.T.