为什么 CancellationTokenSource 会导致应用程序 hang?

3

这里是一个简单的代码片段,它会挂起并永远不会结束:

public static void Main()
{
    using (var cancellationTokenSource = new CancellationTokenSource())
    {
        Console.CancelKeyPress += (_, __) => 
            cancellationTokenSource.Cancel();
        while (!cancellationTokenSource.Token.WaitHandle.WaitOne(1000))
        {
            Console.WriteLine("Still running...");
        }
        Console.WriteLine("Cancellation is requested. Trying to dispose cancellation token...");
    }
    Console.WriteLine("Just before close");
}

问题是Just before close这行代码从未被执行。我的第一个想法是“可能是因为在KeyPress上的闭包”,因此我以以下方式重写了它:
public static void Main()
{
    using (var cancellationTokenSource = new CancellationTokenSource())
    {
        void Foo(object sender, ConsoleCancelEventArgs consoleCancelEventArgs)
        {
            cancellationTokenSource.Cancel();
        }
        Console.CancelKeyPress += Foo;
        while (!cancellationTokenSource.Token.WaitHandle.WaitOne(1000))
        {
            Console.WriteLine("Still running...");
        }
        Console.WriteLine("Cancellation is requested. Unsubscribing...");
        Console.CancelKeyPress -= Foo;
        Console.WriteLine("Cancellation is requested. Trying to dispose cancellation token...");
    }
    Console.WriteLine("Just before close");
}

但现在它停留在取消订阅阶段...

你知道为什么会出现这种情况吗?我想在控制台应用程序中运行一些后台任务并等待其完成,但由于上述原因,我的应用程序已经崩溃了。

1个回答

6
问题不在于你的取消标记,而是你选择使用了CancelKeyPress进行测试。当这个事件发生时,你会得到ConsoleCancelEventArgs,它有一个Cancel属性

获取或设置一个值,该值指示同时按 Control 修改键和 C 控制台键 (Ctrl+C) 或 Ctrl+Break 键是否终止当前进程。默认值为 false,即终止当前进程。

由于你没有将它设置为true,因此你的应用程序在事件处理程序运行完毕后终止。根据当时正在进行的确切操作,似乎有时取消标记有时间打破 while 循环,有时则没有:

test runs

您的原始代码可以按如下方式进行修复:

public static void Main()
{
    using (var cancellationTokenSource = new CancellationTokenSource())
    {
        Console.CancelKeyPress += (_, ccea) => {
            cancellationTokenSource.Cancel();
            ccea.Cancel = true; //cancel the cancel.  There's too many cancels!
        };
        while (!cancellationTokenSource.Token.WaitHandle.WaitOne(1000))
        {
            Console.WriteLine("Still running...");
        }
        Console.WriteLine("Cancellation is requested. Trying to dispose cancellation token...");
    }
    Console.WriteLine("Just before close");
}

1
它不会终止,会永远卡住。但我认为你是对的。 - undefined
@AlexZhukovskiy 你是如何运行它的?如果我运行你的原始代码并按下ctrl-c,我看到这个,所以似乎可能存在一些竞争条件。无论如何,在这里使用CancelKeyEvent会让事情变得混乱。 - undefined
在调试器中,如果没有它,只要我按下Ctrl+F5,它就会在随机的时刻卡住。可能是发生了一些死锁... - undefined
无论如何,这个解决方案对我也有效。最后我订阅了你提到的Console.CancelKeyPressAssemblyLoadContext.Default.Unloading来处理SIGTERM信号。 - undefined
我遇到了与CancelKeyPress相同的问题,Visual Studio会显示下一条语句(黄色箭头),但选择不执行它,永远。应用程序将永远不会终止。对我来说,ccea.Cancel = true解决了这个问题。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接