为什么在任务为 运行 时添加 Console.CancelKeyPress 处理程序会阻止调试器 CTRL+C 退出

Why does adding a Console.CancelKeyPress handler block debugger CTRL+C exit when a Task is running

我已经设法在 VS2017 中使用 .Net Framework 4.6 将其简化为最小测试用例 运行ning:

    static void Main(string[] args)
    {
        Console.CancelKeyPress += (o, e) => End(); //some attempt to exit gracefully
        Task.Run(() => Task.Delay(100000)).Wait();
    }

    private static void End()
    {
        Console.WriteLine("EXITING...");
    }

如果我 运行 在调试器中用 Console.CancelKeyPress 注释掉了这个,CTRL+C 强制终止应用程序。使用编写的代码,它输出 "EXITING..." 然后挂起,即使我的事件处理程序没有采取任何措施来防止终止。

如果我从命令行 运行,两个版本都会按预期退出。

我花了一些时间才弄清楚涉及未完成的 Task 但我不知道为什么事件处理程序正在改变行为。谁能弄清楚为什么?这是一些调试器怪癖吗?我在调试器中没有看到任何错误...

这只是一个大胆的猜测,但是您是否检查过将 ConsoleCancelEventArgs 实例 e 的取消 属性 设置为 false 时会发生什么情况?

如果这能解决您的问题,那就太奇怪了,不过它可能有助于缩小问题范围。

PS: 我无法自己测试,因为我目前在 Linux 机器上。

这是一个 visual studio 调试器问题,我认为它的发生是因为 visual studio 正在分别跟踪所有 运行ning 线程,正如你所说你有一个未完成的任务 运行宁在另一个线程。

当您使用 CTRL+C 从命令行终止程序时,命令行会强制您的主线程停止。这样你的子线程也会停止,但是当你 运行 使用 VS-debugger 主线程的应用程序连接到调试器时。

默认情况下,如果您不处理 Console.CancelKeyPress 事件,调试器将释放所有阻塞线程,但如果您想手动控制终止调用则不会。 (I'm not sure why)

如果您想强制调试器终止所有线程,您必须手动执行此操作。

    private static void End()
    {
      Console.WriteLine("EXITING...");
      if (Debugger.IsAttached)
          Environment.Exit(1); // this is equal to using CTRL+C in the terminal
    }

此外,您应该使用取消令牌在应用程序终止前彻底取消您的 运行ning 任务。

 class Program
    {
        private static CancellationTokenSource tokenSource;
        static async Task Main(string[] args)
        {
            tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;
            Console.CancelKeyPress += (o, e) => End(); 
            await Task.Run(() => Task.Delay(100000), token);
        }

        private static void End()
        {
            Console.WriteLine("EXITING...");
            tokenSource.Cancel();
            tokenSource.Dispose();
            if (Debugger.IsAttached)
                Environment.Exit(1);
        }
    }