简体   繁体   中英

Why doesn't console application exit after last statement when running in Visual Studio 2019 debugger?

I am looking for an async friendly way to wait for ctrl+c to exit a C# console application. The code below works if I run the compiled binary directly. However, if I run it in the debugger, input ctrl+c, and step through I will hit the closing brace of the program but it does not exit. If I comment out the await, the application finishes normally so I don't think it's a VS settings issue.

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;
    }
}

Try this:

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;
}

I tested it with this:

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");

It is very interesting question.

And in Visual Studio when you press pause debug button you can open and see the list of active tasks and/or threads.

I paused the execution.

在此处输入图像描述

And double clicked the thread

Selected "Download Sources and Continue Debugging"

在此处输入图像描述

And came to this code:

在此处输入图像描述

According to the comments and the method name I would come to conclusion that it is related somehow to debug mode and the fact that the execution finished unsuccessfully. And indeed, when I launched it as Ctrl+F5, ie without debugger attached it worked.

在此处输入图像描述

And also you can see there return code is not a zero, which also says that execution was not successfull.

So, it may be related to the debugger and that you cancel the task but don't catch it.

Upd1:

Going further I found that the event registration finally calls SetConsoleCtrlHandler

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

And according to that

Return value If the function handles the control signal, it should return TRUE. If it returns FALSE, the next handler function in the list of handlers for this process is used.

Returned value is the Cancel property of the Cancel...Args parameter.

And they also say

By default, the Cancel property is false, which causes program execution to terminate when the event handler exits. Changing its property to true specifies that the application should continue to execute.

And for me it looks confusing, but I guess you need to set Cancel to true which indicates that the application handled the cancel event.

Upd2:

I changed the Console event with winapi SetConsoleCtrlHandler and the same behavior happens. And if you change false to true in the ConsoleCtrlDelegate, the application will exit successfully. So I think in the debug mode there might be another event handler which blocks execution, but I'm not sure.

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;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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