RogueDeus
RogueDeus

Reputation: 857

Pausing current thread via user input, in console window application

This is the solution I came up with to pause/resume an active thread inside a console...

It requires a second thread for input, which the main loop reacts too. But my ignorance of C# makes me wonder if there is a simpler solution? Perhaps one that can be done with the main thread alone?

Essentially, I am testing several things that will iterate at the defined FPS, and I want to be able to pause and resume the iteration via keyboard input. Any feedback is appreciated.

class TestLoops
{
    int targetMainFPS = 5;
    int targetInputFPS = 3;
    bool CONTINUE = true;

    Thread testLoop, testLoop2;
    ConsoleKeyInfo cki;

    ManualResetEvent resetThread = new ManualResetEvent(true); //How to correctly pause threads in C#.
        public void Resume() { resetThread.Set(); }
        public void Pause() { resetThread.Reset(); }

    public TestLoops()
    {
        //_start = DateTime.Now.Ticks;
        Console.Write("CreatingLoop...");

        this.testLoop = new Thread(MainLoop);
        this.testLoop.Start();

        this.testLoop2 = new Thread(InputLoop);
        this.testLoop2.Start();
    }

    void MainLoop()
    {
        long _current = 0;
        long _last = 0;

        Console.Write("MainLoopStarted ");
        while(CONTINUE)
        {
            resetThread.WaitOne();

            _current = DateTime.Now.Ticks / 1000;
            if(_current > _last + (1000 / targetMainFPS) )
            {
                _last = _current;

                //Do something...
                Console.Write(".");

            }
            else
            {
                System.Threading.Thread.Sleep(10);
            }
        }
    }

    void InputLoop()
    {
        long _current = 0;
        long _last = 0;

        Console.Write("InputLoopStarted ");
        while(CONTINUE)
        {
            _current = DateTime.Now.Ticks / 1000;
            if(_current > _last + (1000 / targetInputFPS))
            {
                _last = _current;

                //Manage keyboard Input
                this.cki = Console.ReadKey(true);
                //Console.Write(":");
                if(this.cki.Key == ConsoleKey.Q)
                {
                    //MessageBox.Show("'Q' was pressed.");
                    CONTINUE = false;
                }

                if(this.cki.Key == ConsoleKey.P)
                {
                    this.Pause();
                }
                if(this.cki.Key == ConsoleKey.R)
                {
                    this.Resume();
                }
            }
            else
            {
                System.Threading.Thread.Sleep(10);
            }
        }
    }

    public static void Main(string[] args)
    {
        TestLoops test = new TestLoops();
    }

}

Upvotes: 0

Views: 2303

Answers (2)

Douglas
Douglas

Reputation: 54877

You can simplify it drastically by using the non-blocking Console.KeyAvailable check. That way, you can execute all your code from the main thread:

using System;
using System.Threading;

class TestLoops
{
    const int targetFPS = 5;

    public static void Main(string[] args)
    {
        bool _continue = true;
        DateTime _last = DateTime.MinValue;

        while (_continue)
        {
            if (Console.KeyAvailable)
            {
                ConsoleKeyInfo cki = Console.ReadKey(true);

                if (cki.Key == ConsoleKey.Q)
                {
                    Console.WriteLine("\n'Q' was pressed.");
                    _continue = false;
                }

                if (cki.Key == ConsoleKey.P)
                {
                    Console.WriteLine("\n'P' was pressed; press 'R' to resume.");

                    // Block until 'R' is pressed.
                    while (Console.ReadKey(true).Key != ConsoleKey.R)
                        ; // Do nothing.

                    Console.WriteLine("'R' was pressed.");
                }
            }

            DateTime _current = DateTime.Now;
            if (_current - _last > TimeSpan.FromMilliseconds(1000F / targetFPS))
            {
                _last = _current;

                // Do something...
                Console.Write(".");
            }
            else
            {
                System.Threading.Thread.Sleep(10);
            }
        }        
    }
}

Edit: Replying to comment.

Be careful. The code below blocks due to the Console.ReadKey call, not due to the while loop. The while loop is there only so that, if the user enters a character other than R, the program would discard it and wait for another character to be entered.

// Block until 'R' is pressed.
while (Console.ReadKey(true).Key != ConsoleKey.R)
    ; // Do nothing.

If you wanted to block until any character is pressed, you would simply use:

Console.ReadKey(true);

Upvotes: 3

vgru
vgru

Reputation: 51204

This will work, yes. The only thing that you could consider is making sure that the worker thread stops even if it was previously suspended, when you press the Q key:

if(this.cki.Key == ConsoleKey.Q)
{
     CONTINUE = false;
     this.Resume(); // <-- make sure that the worker thread resumes
}

As a side note, constant FPS is usually maintained by skipping frames as appropriate, rather than pausing a thread. Pausing a thread is imprecise, and doesn't allow fine tuning animation speeds. Thread.Sleep can also behave differently on different hardware, and can have rather poor resolutions (a minimum OS timeslice is around ~16ms IIRC).

Upvotes: 0

Related Questions