Вызов CancellationTokenSource.Cancel() внутри задачи не устанавливает для Task.IsCanceled значение true

Если я вызываю cancellationTokenSource.Cancel в задаче, связанной с токеном отмены, OperationCancelledException выдается правильно, однако task.IsCanceled НЕ всегда обновляется и устанавливается на true, как можно было бы ожидать.

Проблему можно быстро продемонстрировать с помощью следующего теста nUnit:

var cancellationTokenSource = new CancellationTokenSource();
Task task = Task.Factory.StartNew(() =>
{                                
    cancellationTokenSource.Cancel();
    cancellationTokenSource.Token.ThrowIfCancellationRequested();                
}, 
cancellationTokenSource.Token);

try
{
    task.Wait(cancellationTokenSource.Token);                
}
catch (OperationCanceledException)
{
}

if (task.IsCanceled)
{
    Assert.Pass();
}
else
{
    Assert.Fail();
}

Когда я запускаю этот тест, тест проходит, однако, когда я отлаживаю этот тест (используя средство запуска тестов Resharper), тест завершается ошибкой.

Я не думаю, что это имеет какое-либо отношение к Resharper, я думаю, что Resharper просто может создавать некоторые условия, которые, возможно, выявляют проблему в .Net. Или, может быть, я просто делаю что-то совершенно неправильно... Есть идеи?


person Mikeyg36    schedule 13.01.2015    source источник
comment
У меня работает в консольном приложении... вы установили точки останова, чтобы проверить, например, что Cancel вызывается?   -  person Jon Skeet    schedule 13.01.2015
comment
Да, я прошел через это. В конечном итоге cancellationTokenSource.IsCancellationRequested устанавливается на true, но task.IsCanceled по-прежнему устанавливается на false. Это сбивает с толку.   -  person Mikeyg36    schedule 13.01.2015
comment
Так что же такое task.Status?   -  person Jon Skeet    schedule 13.01.2015
comment
@ Mikeyg36 Майкиг36 Обычно это происходит, если вы не можете передать токен отмены вызову StartNew и используете его только в теле.   -  person Servy    schedule 13.01.2015
comment
Эта штука ведет себя как кот Шрёдингера. Если я добавлю Console.WriteLine("task.Status: {0}", task.Status); прямо перед блоком if, тест пройден. Но если я удалю этот вызов, тест завершится неудачно.   -  person Mikeyg36    schedule 13.01.2015
comment
Я поставил точку останова на Assert.Fail и проверил task.Status в отладчике. он установлен на «Работает».   -  person Mikeyg36    schedule 13.01.2015
comment
@ Mikeyg36 Mikeyg36 Не используйте токен отмены при ожидании задачи. Это приводит к тому, что ожидание сбрасывается и переходит к утверждению до того, как будет установлен статус задачи.   -  person Servy    schedule 13.01.2015


Ответы (1)


Не используйте токен отмены при ожидании Task. Это заставляет Wait бросать и переходить к утверждению до того, как статус задачи будет установлен.

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

person Servy    schedule 13.01.2015
comment
Но разве исключение не должно вызываться только тогда, когда я вызываю cancellationTokenSource.Token.ThrowIfCancellationRequested(), что происходит только один раз? - person Mikeyg36; 13.01.2015
comment
@Mikeyg36 task.Wait(cancellationTokenSource.Token); бросает, если токен отменяется до того, как задача считается выполненной. Он также сбрасывает, если ожидающая задача неисправна/отменена. - person Servy; 13.01.2015
comment
Ах, так что task.wait вызывается, как только вызывается cancellationTokenSource.Cancel(), но затем задача продолжает выполнение асинхронно до cancellationTokenSource.Token.ThrowIfCancellationRequested(), после чего она сама генерирует исключение. Это то, что вы имеете в виду? - person Mikeyg36; 13.01.2015
comment
@ Mikeyg36 Да. Точные сроки могут немного отличаться. Дело просто в том, что Wait может продолжаться до того, как задача будет завершена. Точные возможные переплетения многочисленны. И Wait, и задача, отмеченная как отмененная, происходят через некоторый неопределенное время после отмены токена. Если Wait сначала достигает IsCancelled, ваш тест не пройден. Если он достигает этого второго, ваш тест проходит. - person Servy; 13.01.2015
comment
Но разве это не правильный вариант использования? Разве вы не должны использовать токен отмены именно так: передать его в задачу, Wait для нее, а затем проверить, отменена она или нет. Сказать, что Wait может продолжать работу до того, как задача будет завершена, кажется, что это противоречит основному инварианту Wait, не так ли? - person Mikeyg36; 13.01.2015
comment
@ Mikeyg36 Mikeyg36 Как я уже сказал в ответе, вы не должны передавать токен Wait. Задача уже настроена на самоотмену при отмене токена, поэтому ожидание должно ждать вечно, зная, что ожидающая задача не будет выполняться дольше, чем определяет для нее токен отмены. Вы должны использовать токен отмены с Wait только в том случае, если ожидающая задача не использует токен, и в этом случае вы, очевидно, не ожидаете, что задача будет отменена (или даже завершена) когда Wait завершено. - person Servy; 13.01.2015
comment
Спасибо за все ответы. Я не знал, что токен в Task.Wait использовался для отмены ожидания, а не задачи. Джон Скит на самом деле пролил свет здесь - person Mikeyg36; 13.01.2015