Использование CancellationToken для таймаута в задаче.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();
}

4 ответов


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

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

вы не написали никакого кода внутри вашего Task.Run метод доступа к вашему CancellationToken и реализовать отмену-таким образом, вы фактически проигнорировали запрос на отмену и побежали к завершению.


Я думаю, потому что вы не вызов метода 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 будет немедленно брошен, фактически не начиная задачу.


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

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

при использовании задачи.Работать.(.., CancellationToken) семейство перегрузок с поддержкой отмены маркер отмены проверяется, когда задача собирается запустить. Если маркер отмены имеет iscancellationrequested значение true в это время, исключение типа Создается исключение TaskCanceledException.

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

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

if (токен.IsCancellationRequested) выбросить новое исключение 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.
}

другая реализация с использованием задачи.Задержка с token вместо этого поток.Спать.

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