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

461
12 января 2017, 03:29

Делаю:

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
Стилизация ComboBoxItem

Стилизация ComboBoxItem

Добрый день, столкнулся со следующей проблемкойНе получается задать прозрачность ComboBoxItem

257
Маcштабирование c# wpf

Маcштабирование c# wpf

Доброго времени сутокРазрабатывал приложение на мониторе 4:3 и сейчас купил 16:9

669
Windows Communication Foundation передача классов

Windows Communication Foundation передача классов

Есть некоторое WCF приложениеВ контракте на хосте WCF есть такой метод:

285
c#: Как из асинхронного кода сделать task?

c#: Как из асинхронного кода сделать task?

Здравствуйте, уважаемые

295