Подтвердить что ты не робот

Использование CancellationToken для тайм-аута в Task.Run не работает

Хорошо, мои вопросы очень просты. Почему этот код не бросает 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();
}
4b9b3361

Ответ 1

Отмена в управляемых потоках:

Отмена является совместной и не навязывается слушателю. Слушатель определяет, как изящно завершить работу в ответ на запрос аннулирования.

Вы не писали код внутри метода Task.Run для доступа к вашему CancellationToken и для реализации отмены - поэтому вы фактически проигнорировали запрос об отмене и выполнили его до завершения.

Ответ 2

Я думаю, потому что вы не вызываете метод ThrowIfCancellationRequested() из объекта CancellationToken. Таким образом, вы игнорируете запрос об отмене задания.

Вы должны сделать что-то вроде этого:

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();
}

Второй вариант вашего кода работает, потому что вы уже инициализируете токен с состоянием Canceled, установленным в true. Действительно, как указано здесь:

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

аннулирование уже запрошено, и тогда исключение TaskCanceledException будет немедленно отправлено, не начав задачу.

Ответ 3

Существует разница в отмене запущенной задачи и задача, запланированная для запуска.

После вызова метода Task.Run задача только запланирована и, вероятно, еще не выполнена.

Когда вы используете семейство перегрузок Task.Run(..., CancellationToken) с поддержкой отмены, маркер отмены проверяется, когда задача будет запущена. Если токен отмены имеет значение IsCancellationRequested в настоящее время равным true, исключается исключение типа TaskCanceledException.

Если задача уже запущена, задача состоит в том, чтобы вызвать метод ThrowIfCancellationRequested или просто выбросить исключение OperationCanceledException.

В соответствии с MSDN это просто удобный метод для следующего:

if (token.IsCancellationRequested)     throw new OperationCanceledException (токен);

Не исключение, используемое в этих двух случаях:

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

Также обратите внимание, что TaskCanceledException происходит от OperationCanceledException, поэтому вы можете просто иметь одно предложение catch для типа OperationCanceledException:

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