Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing CancelKeyPress to stop an async console app at a safe point

I'm working on a small utility console app, built in C# 7.1 (which has async Main support).

The app takes one of several input commands and then starts a long-running process which iterates through tens of thousands of items, processing each.

I want to be able to cancel this process at any time (using CTRL+C), although the program should not immediately cancel but instead should finish the current iteration, then stop.

Here's a shortened version of what I have so far.

private static bool _cancel;    

private static async Task Main(string[] args)
{
    Console.CancelKeyPress += (sender, eventArgs) =>
                                      {
                                          eventArgs.Cancel = true;                                              _logger("Migration will be stopped after the current record has been completed.\n");
                                          _cancel = true;
                                      };

    while (!_cancel)
    {
        var input = Console.ReadLine();

        // handle the various input commands
    }
}

Inside the methods which run the (optional) long-running process there is logic which checks for this global _cancel variable:

private static async Task RunPersonMigration(Order order)
{
    var nextPerson = // ...
    while (nextPerson.IsValid)
    {
        // business logic

        if (_cancel)
        {
            _logger("Person migration stopped by user.\n");
            return;
        }

        nextPerson = // ...
    }
}

However, whenever I hit CTRL+C the Visual Studio debugger asks me to locate an assembly, and it's usually a different assembly each time. For example, I've been asked to locate waithandle.cs and thread.cs. Because I'm unable to locate such files, the running debug process abruptly stops.

I can never see which line is causing the problem, and no amount of breakpoints helps.

Basically, I'm trying to use CTRL+C to exit a long-running process without exiting the console app.

Can anyone tell me how I should correctly handle cancelling a long-running console process at a point of my choosing?

UPDATE:
If I update my CancelKeyPress delegate...

Console.CancelKeyPress += (sender, eventArgs) =>
                                  {
                                      **eventArgs.Cancel = true;**                                              _logger("Migration will be stopped after the current record has been completed.\n");
                                      _cancel = true;
                                  };

then this stops the program from crashing to a close, but I'd still like to be able to trap CTRL+C and use it as a means of exiting the long-running process without exiting the console app itself. Is this even possible?

like image 814
awj Avatar asked Jan 12 '18 09:01

awj


1 Answers

A working example using a CancellationToken, which can be passed down to lower levels:

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

namespace ConsoleApp
{
    class Program
    {
        // Cancellation Tokens - https://learn.microsoft.com/en-us/previous-versions/dd997289(v=vs.110)
        private static readonly CancellationTokenSource canToken = new CancellationTokenSource();

        static async Task Main(string[] args)
        {
            Console.WriteLine("Application has started. Ctrl-C to end");

            Console.CancelKeyPress += (sender, eventArgs) =>
            {
                Console.WriteLine("Cancel event triggered");
                canToken.Cancel();
                eventArgs.Cancel = true;
            };

            await Worker();

            Console.WriteLine("Now shutting down");
            await Task.Delay(1000);
        }

        async static Task Worker()
        {
            while (!canToken.IsCancellationRequested)
            {
                // do work       
                Console.WriteLine("Worker is working");
                await Task.Delay(1000); // arbitrary delay
            }
        }
    }
}
like image 132
GFoley83 Avatar answered Oct 13 '22 17:10

GFoley83