简体   繁体   English

CancellationTokenSource.Cancel()挂起

[英]CancellationTokenSource.Cancel() hangs

I'm observing hang in CancellationTokenSource.Cancel when one of the async is in an active loop. 当异步之一处于活动循环中时,我观察到CancellationTokenSource.Cancel挂起。

Full code: 完整代码:

static async Task doStuff(CancellationToken token)
{
    try
    {
        // await Task.Yield();
        await Task.Delay(-1, token);
    }
    catch (TaskCanceledException)
    {
    }

    while (true) ;
}

static void Main(string[] args)
{
    var main = Task.Run(() =>
    {
        using (var csource = new CancellationTokenSource())
        {
            var task = doStuff(csource.Token);
            Console.WriteLine("Spawned");
            csource.Cancel();
            Console.WriteLine("Cancelled");
        }
    });
    main.GetAwaiter().GetResult();
}

Prints Spawned and hangs. 打印Spawned并挂起。 Callstack looks like: 调用栈看起来像:

ConsoleApp9.exe!ConsoleApp9.Program.doStuff(System.Threading.CancellationToken token) Line 23   C#
[Resuming Async Method] 
[External Code] 
ConsoleApp9.exe!ConsoleApp9.Program.Main.AnonymousMethod__1_0() Line 34 C#
[External Code] 

Uncommeting await Task.Yield would result in Spawned\\nCancelled in output. await Task.Yield将在输出中产生Spawned\\nCancelled

Any ideas why? 有什么想法吗? Does C# guarantee that once-yielded async would never block other asyncs? C#是否保证曾经屈服的异步永远不会阻止其他异步?

CancellationTokenSource does not have any notion of task scheduler. CancellationTokenSource没有任务计划程序的任何概念。 If the callback wasn't registered with a custom synchronization context, CancellationTokenSource will execute it in the same callstack as .Cancel() . 如果未在自定义同步上下文中注册回调,则CancellationTokenSource将在与.Cancel()相同的调用堆栈中执行该回调。 In your case, the cancellation callback completes the task returned by Task.Delay , then the continuation is inlined, resulting in an infinite loop inside of CancellationTokenSource.Cancel . 在您的情况下,取消回调将完成Task.Delay返回的任务,然后内联该继续,从而在CancellationTokenSource.Cancel内部导致无限循环。

Your example with Task.Yield only works because of a race condition. 您的Task.Yield示例仅适用于竞争条件。 When the token is cancelled, the thread hasn't started executing Task.Delay , therefore there is no continuation to inline. 取消令牌后,线程尚未开始执行Task.Delay ,因此没有继续进行内联的操作。 If you change your Main to add a pause, you'll see that it'll still freeze even with Task.Yield : 如果将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();
}

Right now, the only way reliable to protect a call to CancellationTokenSource.Cancel is to wrap it in Task.Run . 现在,保护对CancellationTokenSource.Cancel的调用的唯一可靠方法是将其包装在Task.Run

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM