9

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?

awj
  • 7,482
  • 10
  • 66
  • 120
  • It's not clear what you're asking. Ctrl-C has special treatment in a console program, and that includes interrupting the process when you press the key, which the debugger is obliged to inform you of. VS does not _require_ that you find source files. You can simply continue executing the program at that point. You could even debug the assembly code without the source files. From your description, it sounds like there's nothing actually wrong. If you don't want the debugger to step in when the key is pressed, **don't run the program with the debugger attached!** – Peter Duniho Jan 12 '18 at 09:10
  • If I start a long-running process inside a console app, how can I allow the user to stop the process through a command instead of exiting the whole console? How do I pass in "Stop that process you're currently running"? – awj Jan 12 '18 at 09:16
  • So what happens without debugger attached? Crash? Nothing (continues to execute)? – Evk Jan 12 '18 at 09:21
  • @Evk - Yes, it crashes - or immediately closes might be a better description. – awj Jan 12 '18 at 09:25
  • Yes exactly what? :) Crash or nothing happens? If crash than with which exception? – Evk Jan 12 '18 at 09:26
  • 1
    Possible duplicate of https://stackoverflow.com/questions/177856/how-do-i-trap-ctrl-c-in-a-c-sharp-console-app – Peter Duniho Jan 12 '18 at 09:28
  • Possible duplicate of https://stackoverflow.com/questions/474679/capture-console-exit-c-sharp – Peter Duniho Jan 12 '18 at 09:29
  • Possible duplicate of https://stackoverflow.com/questions/203246/how-can-i-keep-a-console-open-until-cancelkeypress-event-is-fired – Peter Duniho Jan 12 '18 at 09:29
  • 1
    From your very vague description, it sounds like you are simply failing to set the `e.Cancel` property to `true` in the `CancelKeyPress` event handler. If you don't do that, Ctrl-C still terminates the program, even when you handle the event. See my proposed duplicates for more details. – Peter Duniho Jan 12 '18 at 09:30
  • @PeterDuniho - Yes, you're right. I wasn't aware of `e.Cancel`. However, I still wish to keep the console app open after the process has been halted. I'm not sure this is even possible. I'll update the OP. – awj Jan 12 '18 at 09:33
  • As I just wrote: if you set `e.Cancel`, your program should not be terminated. From [the documentation](https://msdn.microsoft.com/en-us/library/system.console.cancelkeypress%28v=vs.110%29.aspx): _"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."_. It's amazing what you'll find, if you just look. – Peter Duniho Jan 12 '18 at 09:35
  • 3
    _"I still wish to keep the console app open after the process has been halted. I'm not sure this is even possible"_ -- if you set `e.Cancel` to `true`, the only thing left terminating your program **is your own code**. You've got a loop in your `Main()` method that runs until you set the `_cancel` flag and then your program exits. If you don't want your program to exit, **then don't exit**. – Peter Duniho Jan 12 '18 at 09:46
  • Yes, that's it all wrapped up now, thanks. The `while (!_cancel)` was something I dropped in as I was putting this post together, it had previously been `while (true)`, and putting it back to `true` now has the console app running exactly as I was hoping for. Thanks @PeterDuniho – awj Jan 12 '18 at 09:50

1 Answers1

10

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
            }
        }
    }
}
GFoley83
  • 3,439
  • 2
  • 33
  • 46