Search code examples
c#multithreadingmonitor

How to interrupt a program by a key press


I want to interrupt my producer-consumer program by pressing the key T. I searched a lot of answers but i can't figure out why it is not working.

static void Main(string[] args)
    {
    Buffer buffer = new Buffer();
    Produtor prod = new Produtor(buffer);
    Thread threadProdutor = prod.CriarThreadProdutor();
    Consumidor cons = new Consumidor(buffer, 100000);
    Thread threadConsumidor = cons.CriarThreadConsumidor();

    threadProdutor.Start();
    threadConsumidor.Start();

    threadProdutor.Join();
    threadConsumidor.Join();
    while (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.T)
    {
        Environment.Exit(0);
    }
}

I added a breakpoint in my while, but the program won't even get there.


Solution

  • By putting the loop after your Join() calls the threads will have already completed by the time you check the console for input, so you'll need to reverse the order.

    Further, the while loop will only be entered and continue running if there is a key available and it is T. You want the opposite: loop until a key is available and it is T.

    Finally, Console.ReadKey() blocks until a key is pressed, so you don't need to check Console.KeyAvailable, too, unless you want to do something else while you wait for T (such as display progress or check if the threads completed on their own).

    while (Console.ReadKey(true).Key != ConsoleKey.T)
    {
        // Do nothing...
    }
    // T has been pressed
    
    // Signal to the threads to stop
    // Set a flag, Cancel() a CancellationTokenSource, etc.
    
    // Wait for the threads to terminate
    threadProdutor.Join();
    threadConsumidor.Join();
    
    // Exit the program
    Environment.Exit(0);
    

    To display progress while you wait for the interrupt key, you can rewrite the loop like this...

    TimeSpan progressInterval = TimeSpan.FromSeconds(1);
    
    // complete is a simple flag set by the consumer(s)
    // Only call ReadKey() when KeyAvailable so it can't block longer than updateInterval
    while (!complete && (!Console.KeyAvailable || Console.ReadKey(true).Key != ConsoleKey.T))
    {
        Console.WriteLine($"Current time is {DateTime.Now:HH:mm:ss.fff}");
    
        Thread.Sleep(progressInterval);
    }
    

    Note that this has the downside of always sleeping for the entire progressInterval, even if the exit condition has been satisfied before then. A simple workaround would be to reduce the time between checks to 1 / n and then only display the progress after every nth check...

    TimeSpan progressInterval = TimeSpan.FromSeconds(1);
    const int ReadsPerProgressInterval = 10;
    TimeSpan sleepTimeout = new TimeSpan(progressInterval.Ticks / ReadsPerProgressInterval);
    int readCount = 0;
    
    // complete is a simple flag set by the consumer(s)
    // Only call ReadKey() when KeyAvailable so it can't block longer than updateInterval
    while (!complete && (!Console.KeyAvailable || Console.ReadKey(true).Key != ConsoleKey.T))
    {
        // This won't display progress until after progressInterval has elapsed
        // To display initial progress:
        //     A) change to == 1, or...
        //     B) duplicate progress display to before the loop as well
        if (++readCount % ReadsPerProgressInterval == 0)
            Console.WriteLine($"Current time is {DateTime.Now:HH:mm:ss.fff}");
    
        Thread.Sleep(sleepTimeout);
    }