Search code examples
c#event-handlingtaskcancellation

Program not finishing after trapping Cancel event


Why does this console app not exit when Ctrl+C is pressed?

program output:

Press Ctrl+C to stop...
doing stuff.
doing stuff.
...
*Ctrl+C pressed*
exiting...
*never actually exits*

class Program {
    static void Main(string[] args) {
        MainAsync(args).GetAwaiter().GetResult();
    }

    private static async Task MainAsync(string[] args) {

        MyAsyncClass myAsync = new MyAsyncClass();

        var tcs = new TaskCompletionSource<object>();
        Console.CancelKeyPress += (sender, e) => { tcs.SetResult(null); };

        var task = Task.Run(() => myAsync.Start());

        await Console.Out.WriteLineAsync("Press Ctrl+C to stop...");

        await tcs.Task;
        await Console.Out.WriteLineAsync("exiting...");
    }
}

public class MyAsyncClass {
    public async Task Start() {
        while(true) {
            await Console.Out.WriteLineAsync("doing stuff.");
            Thread.Sleep(1000);
        }
    }
}

Solution

  • You need to set the ConsoleCancelEventArgs.Cancel property to true:

    Console.CancelKeyPress += (sender, e) =>
    {
        tcs.SetResult(null);
        e.Cancel = true;   // <-------- add this to your code
    };
    

    This will allow your code to continue to the end of the program and exit normally, rather than the Ctrl+C to try to terminate the application after the event handler is done.

    Note that in testing, I found that this only seems to matter when the Visual Studio debugger is attached (running with F5). But running without one attached (Ctrl+F5, or just running the compiled .exe) didn't seem to care if this property was set or not. I could not locate any information that explains why this is the case, but my guess is that there's some sort of race condition going on.

    Lastly, it would be good form to pass a CancellationToken into your myAsync.Start method and use it instead of while(true). It would also be better to use await Task.Delay instead of Thread.Sleep (but neither of these is the source of the problem).