Задача не отменяется, как ожидалось

У нас получился следующий сценарий:

class Program
{
    static void Main(string[] args)
    {
        // trigger the delayed function
        trigger();

        // cancel the running task.
        _token.Cancel();

        // keep window open ;-)
        Console.ReadLine();

    }

    private static CancellationTokenSource _token = null;
    private static async void trigger()
    {
        _token = new CancellationTokenSource();

        try
        {
            // run task
            await Task.Run(async () =>
            {
                // wait time
                await Task.Delay(2500);

                // we should be cancelled here !!
                Console.WriteLine(string.Format("IsCancellationRequested={0}", _token.Token.IsCancellationRequested));
                Console.WriteLine("SHOULD NOT HAPPEN");

            }, _token.Token);
        }
        catch (TaskCanceledException)
        {
        }
    }
}

IMO ожидаемое поведение заключается в том, что операция задачи отменяется в основном после обработки Task.Delay(2500).

Но консоль печатает:

IsCancellationRequested=True
SHOULD NOT HAPPEN

Это просто похоже на ошибку. Если вы добавите параметр CancellationToken в качестве параметра Task.Delay-Function, он будет работать должным образом.

Итак, как обработать отмену, если функция внутри задачи использует Task.Delay, о чем вы можете не знать?


person Jens Marchewka    schedule 10.07.2015    source источник


Ответы (2)


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

Чтобы на самом деле отменить задачу, вам нужно проверить токен внутри самой задачи. Если вы хотите, чтобы задача была отменена во время задержки, вам нужно передать токен методу Task.Delay. В противном случае вы можете проверить только до или после задержки:

await Task.Run(async () =>
{
    _token.Token.ThrowIfCancelltionRequested();
    await Task.Delay(2500, _token.Token);
    _token.Token.ThrowIfCancelltionRequested();

    Console.WriteLine(string.Format("IsCancellationRequested={0}", _token.Token.IsCancellationRequested));
    Console.WriteLine("SHOULD NOT HAPPEN");

}, _token.Token);
person i3arnon    schedule 10.07.2015
comment
Кроме того, вы все равно не должны обращаться к CancellationTokenSource в Задаче, кроме самого токена. Посмотрите на пример MSDN - person Frank J; 10.07.2015
comment
Но если я передам токен в Task.Delay, он сгенерирует исключение, которое приведет к потере производительности, я прав? - person Jens Marchewka; 10.07.2015
comment
Для меня также недостаточно ясно, почему я могу передать токен через Task.Run в качестве второго параметра. Мне это кажется бессмысленным. - person Jens Marchewka; 13.07.2015
comment
@ElMarchewko Вы передаете токен по двум причинам: 1. Если токен был отменен еще до запуска задачи, то он будет отменен заранее перед запуском. 2. Когда вы вызываете ThrowIfCancelltionRequested, выдается только OperationCanceledException, а возвращаемая задача помечается как сбой. Если вы передаете CancellationToken в Task.Run, он проверяет, соответствует ли OperationCanceledException.CancellationToken тому, которое было передано, и только тогда задача считается отмененой. - person i3arnon; 13.07.2015

Вот хорошая статья MSDN: https://msdn.microsoft.com/en-us/library/dd537607(v=vs.110).aspx

Вызывающий поток не завершает задачу принудительно; это только сигнализирует о том, что запрашивается отмена. Если задача уже запущена, пользовательский делегат должен заметить запрос и отреагировать соответствующим образом. Если отмена запрашивается до запуска задачи, то пользовательский делегат никогда не выполняется, а объект задачи переходит в состояние Canceled.

Как предложил i3arnon, вы можете проверить состояние и создать исключение перед выполнением фактической работы. Выдержка кода из статьи:

...
  // Was cancellation already requested?  
      if (ct.IsCancellationRequested == true) {
         Console.WriteLine("Task {0} was cancelled before it got started.",
                           taskNum);
         ct.ThrowIfCancellationRequested();
      } 
...
person Volkan Paksoy    schedule 10.07.2015