c# как работает скоординированная отмена?
Во первых не понимаю почему поведение кода снизу отличается в Debug и Release:
- в Debug задача выводит числа до 10 и после отменяется,
- в Release она отменяется сразу и числа не успевают вывестись (хотя отмена вроде как называется скоординированной, но задача завершается без участия кода внутри задачи).
Но при этом, если в Release подождать между запуском и отменой (или вызвать CancelAfter(1000) вместо Cancel()), то задача будет выполняться так же как в Debug, пока ее не отменят изнутри
public class Program
{
public static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Run(() => Print(cts.Token), cts.Token);
//отменяется сразу в Release
cts.Cancel();
Console.ReadKey();
}
static void Print(CancellationToken token)
{
int i = 0;
while (true)
{
i++;
Thread.Sleep(1000);
Console.WriteLine(i);
if (i == 10) token.ThrowIfCancellationRequested();
}
}
}
Ответы (1 шт):
Можно, конечно, начать гадать, почему поведение различается под дебагом и релизом, но главное тут то, что вы неправильно используете асинхронность, а в этом случае поведение может быть какое угодно не предсказуемое.
"Вылечить" ваш код довольно просто, переведя его полностью в асинхронную версию:
static async Task Print(CancellationToken token)
^^^^^ ^^^^
{
int i = 0;
while (true)
{
i++;
await Task.Delay(1000, token);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Console.WriteLine(i);
if (i == 10) token.ThrowIfCancellationRequested();
}
}
В этом коде отмена происходит, естественно, моментально, как и задумано авторами .NET Core. А когда вы используете void метод и внутри него усыпляете поток, не используя асинхронность - да бог весть как это должно себя вести, да как угодно вообще, ибо асинхронность и многопоточность - это две разные концепции, они не обязаны дружить предсказуемым образом, когда они используются не в рамках парадигмы асинхронности, а каждая сама по себе.
P.S. Могу только предположить, что в Release по каким-то причинам старт таски происходит чуть позже, cts.Cancel(); успевает отработать раньше, чем Task.Run проверит токен на кансельнутость перед тем, как реально запустить таску. Почему так - нужно смотреть сгенерированный код. А в Debug наоборот - таска стартует, а уже после этого отрабатывает cts.Cancel();, но он уже не может повлиять на уже запущенную таску, поскольку в ней вызывается void метод, в котором нет никаких await, он просто спокойно выполняется в своём потоке, никак не взаимодействуя с механизмом асинхронности и его средствами передачи сигналов. Но, конечно, явная проверка token.ThrowIfCancellationRequested(); при этом срабатывает.