CancellationTokenSource
не имеет никакого понятия о планировщике задач.Если обратный вызов не был зарегистрирован в пользовательском контексте синхронизации, CancellationTokenSource выполнит его в том же стеке вызовов, что и .Cancel()
.В вашем случае обратный вызов отмены завершает задачу, возвращаемую Task.Delay
, затем продолжение встраивается, что приводит к бесконечному циклу внутри CancellationTokenSource.Cancel
.
Ваш пример с Task.Yield
работает только из-засостояние гонки.Когда токен отменен, поток не начал выполнять Task.Delay
, поэтому нет продолжения для inline.Если вы измените свой Main
, чтобы добавить паузу, вы увидите, что он по-прежнему будет зависать даже с Task.Yield
:
static void Main(string[] args)
{
var main = Task.Run(() =>
{
using (var csource = new CancellationTokenSource())
{
var task = doStuff(csource.Token);
Console.WriteLine("Spawned");
Thread.Sleep(1000); // Give enough time to reach Task.Delay
csource.Cancel();
Console.WriteLine("Cancelled");
}
});
main.GetAwaiter().GetResult();
}
Прямо сейчас, единственным надежным способом защитить вызов CancellationTokenSource.Cancel
- это завернуть в Task.Run
.