131

Consider a Console application that starts up some services in a separate thread. All it needs to do is wait for the user to press Ctrl+C to shut it down.

Which of the following is the better way to do this?

static ManualResetEvent _quitEvent = new ManualResetEvent(false);

static void Main() {
    Console.CancelKeyPress += (sender, eArgs) => {
        _quitEvent.Set();
        eArgs.Cancel = true;
    };

    // kick off asynchronous stuff 

    _quitEvent.WaitOne();

    // cleanup/shutdown and quit
}

Or this, using Thread.Sleep(1):

static bool _quitFlag = false;

static void Main() {
    Console.CancelKeyPress += delegate {
        _quitFlag = true;
    };

    // kick off asynchronous stuff 

    while (!_quitFlag) {
        Thread.Sleep(1);
    }

    // cleanup/shutdown and quit
}
intoOrbit
  • 2,122
  • 2
  • 19
  • 21

9 Answers9

80

you always want to prevent using while loops, especially when you are forcing the code to recheck variables. It wastes CPU resources and slows down your program.

I would definitely say the first one.

Mike
  • 1,412
  • 16
  • 24
  • 3
    +1. In addition, since the `bool` isn't declared as `volatile`, there is the definite possibility that subsequent reads to `_quitFlag` in the `while` loop would be optimized away, leading to an infinite loop. – Adam Robinson Apr 06 '10 at 16:51
  • 2
    Missing the recommended way to do this. I was expecting this as an answer. – Iúri dos Anjos Apr 23 '18 at 17:37
35

Alternatively, a more simple solution is just:

Console.ReadLine();
Cocowalla
  • 13,822
  • 6
  • 66
  • 112
  • I was about to suggest that, but it won't stop only on Ctrl-C – Thomas Levesque Apr 06 '10 at 16:52
  • I got the impression that CTRL-C was just an example - any user input to close it – Cocowalla Apr 06 '10 at 16:55
  • 2
    Remember that 'Console.ReadLine()' is thread blocking. So the application would still be running but doing nothing than waiting the user enter a line – fabriciorissetto Sep 30 '15 at 13:24
  • 3
    @fabriciorissetto The OP question states 'kick off asynchronous stuff', so the application will be performing work on another thread – Cocowalla Oct 01 '15 at 06:51
  • 1
    @Cocowalla I missed that. My bad! – fabriciorissetto Oct 01 '15 at 17:04
  • What I just recognized is that with an Console.ReadLine at the end of the main, the application stops immediately and maybe don't execute the full code in the CancelKeyPress-function – ZerOne Apr 26 '16 at 12:46
  • `Console.ReadLine` was a source of a long-running mystery for us. It contributed to deadlock when used in conjunction with `Console.WriteLine`. This would happen whenever we clicked on the console window in such a way that that the console waited for input. – Little Endian Jun 07 '19 at 15:05
  • Console.ReadLine might be ignored and skipped entierly if the Console App runs as a child process. ManualResetEvent does seem to work though. – oddbear Feb 01 '21 at 11:43
  • This doesn't work when the program is started with console detached. Specifically, it won't work as a daemon on linux. – Daniel Dror May 06 '23 at 00:19
15

You can do that (and remove the CancelKeyPress event handler) :

while(!_quitFlag)
{
    var keyInfo = Console.ReadKey();
    _quitFlag = keyInfo.Key == ConsoleKey.C
             && keyInfo.Modifiers == ConsoleModifiers.Control;
}

Not sure if that's better, but I don't like the idea of calling Thread.Sleep in a loop.. I think it's cleaner to block on user input.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
9

It's also possible to block the thread / program based on a cancellation token.

token.WaitHandle.WaitOne();

WaitHandle is signalled when the token is cancelled.

I have seen this technique used by the Microsoft.Azure.WebJobs.JobHost, where the token comes from a cancellation token source of the WebJobsShutdownWatcher (a file watcher that ends the job).

This gives some control over when the program can end.

CRice
  • 12,279
  • 7
  • 57
  • 84
  • 1
    This is an excellent answer for any real-world Console app that needs to listen for a `CTL+C` because it is performing a long-running operation, or is a daemon, that should also gracefully shutdown its worker threads. You would do that with a CancelToken and so this answer takes advantage of a `WaitHandle` that would already exist, rather than creating a new one. – mdisibio May 21 '20 at 00:02
8

I prefer using the Application.Run

static void Main(string[] args) {

   //Do your stuff here

   System.Windows.Forms.Application.Run();

   //Cleanup/Before Quit
}

from the docs:

Begins running a standard application message loop on the current thread, without a form.

ygaradon
  • 2,198
  • 2
  • 21
  • 27
  • 14
    But then you take a dependency on windows forms just for this. Not too much of an issue with the traditional .NET framework, but the current trend is towards modular deployments including only the parts you need. – CodesInChaos Jan 30 '15 at 14:10
4

Seems like you're making it harder than you need to. Why not just Join the thread after you've signaled it to stop?

class Program
{
    static void Main(string[] args)
    {
        Worker worker = new Worker();
        Thread t = new Thread(worker.DoWork);
        t.IsBackground = true;
        t.Start();

        while (true)
        {
            var keyInfo = Console.ReadKey();
            if (keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers == ConsoleModifiers.Control)
            {
                worker.KeepGoing = false;
                break;
            }
        }
        t.Join();
    }
}

class Worker
{
    public bool KeepGoing { get; set; }

    public Worker()
    {
        KeepGoing = true;
    }

    public void DoWork()
    {
        while (KeepGoing)
        {
            Console.WriteLine("Ding");
            Thread.Sleep(200);
        }
    }
}
Ed Power
  • 8,310
  • 3
  • 36
  • 42
  • 2
    In my case I don't control the threads that the asynchronous stuff runs on. – intoOrbit Apr 07 '10 at 18:41
  • 1) I don't like that you're checking for the keys Ctrl+C, instead of the signal triggered by Ctrl+C. 2) Your approach doesn't work if the application uses Tasks instead of a single worker thread. – CodesInChaos Jan 30 '15 at 14:11
1

Of the two first one is better

_quitEvent.WaitOne();

because in the second one the thread wakes up every one millisecond will get turned in to OS interrupt which is expensive

Naveen
  • 4,092
  • 29
  • 31
  • 2
    This is a good alternative for `Console` methods if you don't have a console attached (because, for example, the program is started by a service) – Marco Sulla Jun 28 '16 at 14:25
0

You should do it just like you would if you were programming a windows service. You would never use a while statement instead you would use a delegate. WaitOne() is generally used while waiting for threads to dispose - Thread.Sleep() - is not advisible - Have you thought of using System.Timers.Timer using that event to check for shut down event?

KenL
  • 865
  • 5
  • 14
-1

You may run as host

    public class Program
{
    /// <summary>
    /// Application entry point
    /// </summary>
    /// <param name="args">arguments</param>
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

where HostBuilder is

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureServices((_, services) =>
            {
            })
            .ConfigureLogging(config =>
            {
                config.ClearProviders();
            });
NizMaS
  • 47
  • 3