Как работает CancellationToken в TaskFactory.StartNew Method (Action, CancellationToken)?

159
26 февраля 2019, 00:00

Делаю:

Task.Factory.StartNew(() =>Method(),ct)

В другом месте вызываю tokenSource.Cancel();, но ничего не происходит.

Мне казалось, что такая перегрузка должна полностью задачу снимать.

Получается, что нужно токен передавать непосредственно в сам метод и там производить анализ?

Можно ли как-нибудь отменить задачу, непередавая во внутрь токен?

Answer 1

Смотрите.

Вы не можете «отменить» уже бегущий код — по тем же причинам, по которым вы не можете «убить» бегущий thread. Поэтому код, запускаемый через Task.Run, добежит до конца, если только он сам не будет анализировать токен и не отменит себя сам.

Однако, таск не будет запущен, если токен находится уже в отменённом состоянии. Поскольку запуск Task'а — расходная штука, это неплохая оптимизация.

Кроме того, таск при этом будет ассоциирован с этим токеном, и если код в Method бросит исключение через token.ThrowIfCancellationRequested, это исключение будет считаться относящимся к таску. Тем самым таск будет завершён в состоянии Cancelled, а не Faulted.

Источник информации: https://social.msdn.microsoft.com/Forums/en-US/c2f614f6-c96c-4821-84cc-050b21aaee45/taskfactorystartnew-cancellation-token-parameter?forum=parallelextensions

Воспроизводящий пример:

async Task Run()
{
    var cts = new CancellationTokenSource();
    var ct = cts.Token;
    var t = Task.Run((Action)(() => // каст нужен! пояснение в конце ответа
        {
            while (true)
            {
                ct.ThrowIfCancellationRequested();
                Thread.Sleep(200);
            }
        }), cts.Token);
    await Task.Delay(400);
    cts.Cancel();
    try
    {
        await t;
    }
    catch (TaskCanceledException ex)
    {
        Console.WriteLine($"caught TaskCanceledException, task status = {t.Status}");
    }
    catch (OperationCanceledException ex)
    {
        Console.WriteLine($"caught OperationCanceledException, task status = {t.Status}");
    }
}

Этот код попадает в catch (OperationCanceledException ex), и состояние таска — Canceled. А если убрать токен, то мы попадаем также в catch (OperationCanceledException ex), но состояние — Faulted. В этом случае рантайм считает, что произошла не отмена таска, а просто какое-то постороннее исключение.

TaskCanceledException мы попадаем, если таск был отменён до запуска.)

(Ошибка в предыдущей версии кода была в том, что не было явного приведения к Action [которое практически никогда не нужно!], и ввиду бесконечного цикла внутри типовыводитель не мог решить, это перегрузка с Action или перегрузка с Func<Task>! В результате он выбирал не то, что нужно, и рантайм считал, что отменён не таск, а процесс его создания.)

Answer 2

Да, токен необходимо передавать в метод, и там периодически вызывать ThrowIfCancellationRequested(). Однако, это не отменяет необходимости передавать его вторым параметром StartNew(). Это делается на тот случай, если отмена операции запрошена раньше чем начал выполняться ваш метод (а так же для того, чтобы связать таск с токеном). Примерно так:

var cts = new CancellationTokenSource();
Task.Factory.StartNew(() =>
    {
        while (true)
        {
            cts.Token.ThrowIfCancellationRequested();
            Console.WriteLine("I'm working...");
        }
    }, cts.Token);
cts.CancelAfter(10);

Если была запрошена отмена, то метод ThrowIfCancellationRequested() кинет OperationCanceledException. При желании, его можно поймать и сообщить юзеру, что операция успешно отменена, например.

Если хочется, то можно обходиться без эксепшена, а просто в методе проверять:

if (token.IsCancellationRequested)
{
    /*выходим из метода*/
}

Но тогда сама задача не будет знать о том, что её отменили. В свойстве Status у неё будет TaskStatus.RanToCompletion вместо TaskStatus.Canceled.

UPD: Дополнительный материал по теме: https://msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx

READ ALSO
Как разобрать txt файл?

Как разобрать txt файл?

Есть txt файл со следующей информацией:

192
Почему не обрабатывается сеттер?

Почему не обрабатывается сеттер?

У меня есть некоторое окно, вьюМодель к ней и контролОкно:

165
Растянуть TextBlock на всю ячейку grid WPF

Растянуть TextBlock на всю ячейку grid WPF

У меня есть ListBox, в котором каждый Item представляет собой Grid из трех колонокПервая и последняя колонка имеют ширину 50, а в средней лежит TextBlock,...

169
Правильно ли выполняется проверка на то что флаги совпадают?

Правильно ли выполняется проверка на то что флаги совпадают?

Имеется E'нумератор помеченный аттрибутом [Flags]:

160