Делаю:
Task.Factory.StartNew(() =>Method(),ct)
В другом месте вызываю tokenSource.Cancel();
, но ничего не происходит.
Мне казалось, что такая перегрузка должна полностью задачу снимать.
Получается, что нужно токен передавать непосредственно в сам метод и там производить анализ?
Можно ли как-нибудь отменить задачу, непередавая во внутрь токен?
Смотрите.
Вы не можете «отменить» уже бегущий код — по тем же причинам, по которым вы не можете «убить» бегущий 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>
! В результате он выбирал не то, что нужно, и рантайм считал, что отменён не таск, а процесс его создания.)
Да, токен необходимо передавать в метод, и там периодически вызывать 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
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
У меня есть ListBox, в котором каждый Item представляет собой Grid из трех колонокПервая и последняя колонка имеют ширину 50, а в средней лежит TextBlock,...
Имеется E'нумератор помеченный аттрибутом [Flags]: