How to exit gracefully from a while loop when CancellationTokenSource.Cancel() is used

228 views Asked by At

I have the following program written in dotnet 6:

var cancellationTokenSource = new CancellationTokenSource();

Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) =>
{
    cancellationTokenSource.Cancel();
};

await DoAsync(cancellationTokenSource.Token);

Console.WriteLine("Finished!");

async Task DoAsync(CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        Console.WriteLine("Test!");
        await Task.Delay(1000);
    }
}

When I press "Ctrl + C" the program exits with the following error code: "Program.exe exited with code -1073741510". "Finished!" is never written to the screen.

I cannot understand what exactly happens but it seems that cancellationTokenSource.Cancel() throws an exception and the program finishes with error. As far as I understand, when cancellationTokenSource.Cancel() is called cancellationTokenSource.Token.IsCancellationRequested is set to true and no exception should be thrown.

What is the reason the applicatiion to finish with exception and how can I gracefully exit the application when the user presses "Ctrl + C"?

I googled a lot but couldn't find the answer.

2

There are 2 answers

1
Panagiotis Kanavos On BEST ANSWER

Ctrl+C kills the application. As the CancelKeyPress documentation explains, handling the event won't prevent termination.

To avoid termination you need to set ConsoleCancelEventArgs.Cancel to true

Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) =>
{
    e.Cancel=true;
    cancellationTokenSource.Cancel();
};
0
canton7 On

(Not a proper answer, but too long for a comment).

Apart from the main issue, which Panagiotis Kanavos described, you'll hit a problem where your application takes up to a second to cancel, because the Task.Delay doesn't know about the CancellationToken.

The solution is to embrace exceptions as a cancellation mechanism:

var cancellationTokenSource = new CancellationTokenSource();

Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) =>
{
    e.Cancel = true;
    cancellationTokenSource.Cancel();
};

try
{
    await DoAsync(cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
}

Console.WriteLine("Finished!");

async Task DoAsync(CancellationToken token)
{
    while (true)
    {
        // We'll just let Task.Delay throw an exception when the token is cancelled
        // If we were doing more work in here, we'd call
        // token.ThrowIfCancellationRequested() as a suitable point
        Console.WriteLine("Test!");
        await Task.Delay(1000, token);
    }
}