【问题标题】:Why doesn't console application exit after last statement when running in Visual Studio 2019 debugger?在 Visual Studio 2019 调试器中运行时,为什么控制台应用程序在最后一条语句后不退出?
【发布时间】:2020-06-24 18:03:49
【问题描述】:

我正在寻找一种异步友好的方式来等待 ctrl+c 退出 C# 控制台应用程序。如果我直接运行已编译的二进制文件,则下面的代码有效。但是,如果我在调试器中运行它,输入 ctrl+c 并单步执行,我将点击程序的右大括号,但它不会退出。如果我注释掉等待,应用程序会正常完成,所以我认为这不是 VS 设置问题。

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Console.WriteLine("Hello World");
        await UntilCancelled().ConfigureAwait(true);
        Console.WriteLine("Goodbye Cruel World");
    }

    /// <summary>
    /// Waits until Ctrl+c or Ctrl+Break is entered into the console
    /// </summary>
    /// <param name="cancellationToken">A passed in cancellation token can also cause the await to complete</param>
    /// <returns>True when cancelled</returns>
    public static Task UntilCancelled(CancellationToken cancellationToken = default)
    {
        var cts = new CancellationTokenSource();
        //link tokens if caller wants to have ability to cancel for other conditions
        if (default != cancellationToken)
            cts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);

        //Attach cancellation to event for exiting the console
        Console.CancelKeyPress += (sender, cpe) => cts.Cancel();

        var tcs = new TaskCompletionSource<bool>();
        cts.Token.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
        return tcs.Task;
    }
}

【问题讨论】:

  • 我认为你需要在这里的某个地方“等待”,但我对异步编程的了解不够多,无法提供其他建议。
  • @MikeBruno 我想你错过了这一行:await UntilCancelled().ConfigureAwait(true);

标签: c# visual-studio debugging async-await


【解决方案1】:

这是一个非常有趣的问题。

在 Visual Studio 中,当您按下暂停调试按钮时,您可以打开并查看活动任务和/或线程的列表。

我暂停了执行。

然后双击线程

选择“下载源并继续调试”

来到了这段代码:

根据 cmets 和方法名称,我得出的结论是,它与调试模式和执行未成功完成有关。事实上,当我以 Ctrl+F5 启动它时,即没有附加调试器,它就可以工作了。

您还可以看到返回码不是零,这也表示执行不成功。

因此,它可能与调试器有关,并且您取消了任务但没有捕获它。

更新1:

更进一步我found,事件注册最终调用SetConsoleCtrlHandler

https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler

据此

返回值 如果函数处理控制信号,它应该返回 TRUE。如果它返回 FALSE,则使用该进程的处理程序列表中的下一个处理程序函数。

返回值是 Cancel...Args 参数的 Cancel 属性。

他们也说

默认情况下,Cancel 属性为 false,这会导致程序执行在事件处理程序退出时终止。将其属性更改为 true 指定应用程序应继续执行。

对我来说,这看起来令人困惑,但我想您需要将 Cancel 设置为 true,这表明应用程序处理了取消事件。

更新2:

我用 winapi SetConsoleCtrlHandler 更改了控制台事件,并且发生了相同的行为。如果在 ConsoleCtrlDelegate 中将 false 更改为 true,应用程序将成功退出。所以我认为在调试模式下可能会有另一个事件处理程序阻止执行,但我不确定。

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        Console.WriteLine("Hello World");
        await UntilCancelled();
        Console.WriteLine("Goodbye Cruel World");
    }

    [DllImport("kernel32.dll")]
    static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine,
   bool Add);

    // Delegate type to be used as the Handler Routine for SCCH
    delegate Boolean ConsoleCtrlDelegate(CtrlTypes CtrlType);

    // Enumerated type for the control messages sent to the handler routine
    enum CtrlTypes : uint
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT,
        CTRL_CLOSE_EVENT,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT
    }

    /// <summary>
    /// Waits until Ctrl+c or Ctrl+Break is entered into the console
    /// </summary>
    /// <param name="cancellationToken">A passed in cancellation token can also cause the await to complete</param>
    /// <returns>True when cancelled</returns>
    public static Task UntilCancelled()
    {
        var cts = new CancellationTokenSource();

        ConsoleCtrlDelegate handler = (cpe) =>
        {
            cts.Cancel();

            return false;
        };

        SetConsoleCtrlHandler(handler, true);

        var tcs = new TaskCompletionSource<bool>();

        cts.Token.Register(s => tcs.SetResult(true), null);
        return tcs.Task;
    }
}

【讨论】:

    【解决方案2】:

    试试这个:

    public static Task UntilCancelled(CancellationToken cancellationToken = default)
    {
        var tcs = new TaskCompletionSource<bool>();
        cancellationToken.Register(() => tcs.TrySetResult(true));
        Console.CancelKeyPress += (sender, cpe) =>
        {
            // this will prevent windows from interfering with the shutdown process
            cpe.Cancel = true; 
            tcs.TrySetResult(true);
        };
    
        return tcs.Task;
    }
    

    我用这个测试了它:

    var cts = new CancellationTokenSource();
    Console.WriteLine("Started");
    Task.Run(async () =>
    {
        await Task.Delay(5000);
        cts.Cancel(false);
    }).GetAwaiter();
    await Test.UntilCancelled(cts.Token);
    Console.WriteLine("Canceled");
    

    【讨论】:

    • 真的就像将ConsoleCancelEventArgs Cancel 属性设置为true 一样简单
    猜你喜欢
    • 1970-01-01
    • 2019-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-23
    相关资源
    最近更新 更多