#c_sharp #net #task
Делаю: Task.Factory.StartNew(() =>Method(),ct) В другом месте вызываю tokenSource.Cancel();, но ничего не происходит. Мне казалось, что такая перегрузка должна полностью задачу снимать. Получается, что нужно токен передавать непосредственно в сам метод и там производить анализ? Можно ли как-нибудь отменить задачу, непередавая во внутрь токен?
Ответы
Ответ 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! В результате он выбирал не то, что нужно, и рантайм считал, что отменён не таск, а процесс его создания.) Ответ 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
Комментариев нет:
Отправить комментарий