Использование 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();
}
Ответы
Ответ 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.
}