9

I have a fairly simple console application written in .NET. Sometimes the application is run in batch mode without an operator, other times it is run "out of pocket". If it's running in batch mode, there is a defined default option which allows the program to run automatically. If there is an operator present, there are other options which allow the user to select from a list of functions.

For reasons I don't want to go into, command-line parameters are not preferred. Instead, I've created a 10-second window in which an operator may choose a function. Currently, I'm using a simple while loop and reading input from the "in" stream. I've added a Thread.Sleep call at the end to prevent the while loop from completely consuming the processor, but I'd like to know if there's a better way.

In a Windows application (Windows Forms or WPF) there is a message pump which is able to read the queue of messages and then return control to the system. Even heavy-duty applications like Visual Studio, SAS Enterprise Guide and SQL Server Management Studio use practically 0% of the processor when they are idle. Can I get the same effect with my console application?

The Thread.Sleep is working, but, as I said, I want to know if there's a better way.

Here's the source code:

class Program {
    static void Main( string[] args ) {

        DateTime l_startTime = DateTime.Now;

        Console.CursorVisible = false;
        Console.WriteLine( "Please select an option within 10 seconds..." );
        Console.WriteLine( "" );
        Console.WriteLine( " [1] Option A (DEFAULT)" );
        Console.WriteLine( " [2] Option 2" );
        Console.WriteLine( " [3] Option III" );

        int l_elapsedSeconds = 0;

        bool l_exit = false;
        while ( !l_exit ) {

            int l_currentElapsedSeconds = (int) Math.Floor( ( DateTime.Now - l_startTime ).TotalSeconds );
            if ( l_currentElapsedSeconds > l_elapsedSeconds ) {
                Console.CursorTop = 0;
                Console.CursorLeft = 0;
                l_elapsedSeconds = l_currentElapsedSeconds;

                int l_remainingSeconds = 10 - l_elapsedSeconds;

                Console.WriteLine( String.Format( "{0,-80}", "Please select an option within " + l_remainingSeconds + " seconds..." ) );
            }

            if ( l_elapsedSeconds >= 10 ) {
                OptionA();
                break;
            }

            if ( Console.KeyAvailable ) {
                var l_key = Console.ReadKey( true );

                switch ( l_key.Key ) {
                    case ConsoleKey.D1:
                        OptionA();
                        l_exit = true;
                        break;

                    case ConsoleKey.D2:
                        Option2();
                        l_exit = true;
                        break;

                    case ConsoleKey.D3:
                        OptionIII();
                        l_exit = true;
                        break;
                }
            }

            if ( !l_exit )
                // Don't eat all the processor
                System.Threading.Thread.Sleep( 100);
        }

        Console.CursorTop = 7;
        Console.CursorLeft = 0;

        Console.Write( "Press any key to continue...");
        Console.ReadKey( true);

    }

    static void OptionA() {
        Console.CursorTop = 6;
        Console.CursorLeft = 0;

        Console.WriteLine( "Option A Selected!");
    }

    static void Option2() {
        Console.CursorTop = 6;
        Console.CursorLeft = 0;

        Console.WriteLine( "Option 2 Selected!");
    }

    static void OptionIII() {
        Console.CursorTop = 6;
        Console.CursorLeft = 0;

        Console.WriteLine( "Option III Selected!");
    }
}

Note: This question is not concerned about a timeout... it is about using 0% processor time while waiting for a response (like a windowed application).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JDB
  • 25,172
  • 5
  • 72
  • 123
  • Looks like a good approach to me. I played around a bit and didn't see anything that stands out as being any better. – Servy Aug 31 '12 at 19:56

2 Answers2

10

You could start a thread that reads key presses in the background. Add the keys to a blocking queue and wait in the main thread for the queue to be filled, e.g.

var queue = new BlockingCollection<ConsoleKeyInfo>();

new Thread(() =>
{
    while (true) queue.Add(Console.ReadKey(true));
})
{ IsBackground = true }.Start();


Console.Write("Welcome! Please press a key: ");

ConsoleKeyInfo cki;

if (queue.TryTake(out cki, TimeSpan.FromSeconds(10))) //wait for up to 10 seconds
{
    Console.WriteLine();
    Console.WriteLine("You pressed '{0}'", cki.Key);
}
else
{
    Console.WriteLine();
    Console.WriteLine("You did not press a key");
}

Both the background thread and the main thread will sleep (utilize 0% processor time) while they're waiting for ReadKey and TryTake to return respectively.

dtb
  • 213,145
  • 36
  • 401
  • 431
  • +1 Argh! I love this answer, but my company is still stuck in the Middle Ages (.NET 3.5) for at least another 6 months. I'll have to wait until I get home to try this. – JDB Aug 31 '12 at 20:29
  • On second glance, while I like the approach, I still want to do some processing in the background (a little countdown timer so the user knows how much time is left to press a key). Making the the thread sleep for the entire 10 seconds is not exactly what I want to do, but I imagine this approach could be retooled a bit to get the desired behavior. If the question wasn't closed, I'd hope for more answers, but now it looks like this is the only one I'll get. – JDB Sep 04 '12 at 15:03
0

You could use the event handling mechanism of .NET. Reposting from here.

Imports System.Threading.Thread

Module Module1
    Private Second As Integer = 1
    Private Tick As New Timers.Timer(1000)

    Sub Main()

        AddHandler tick.Elapsed, AddressOf Ticker 'Event for the timer tick

        Dim NewThread As System.Threading.Thread = New System.Threading.Thread(AddressOf LookForKeyPress) 'New thread to check for key press
        NewThread.Start() 'Start thread

        Console.WriteLine("Now awaiting key presses...")
        tick.Enabled = True 'Enable the timer
    End Sub

    Private Sub Ticker()
        'every tick this sub is called and the seconds are displayed till a key is pressed
        Console.WriteLine(Second)
        Second += 1

    End Sub

    Private Sub LookForKeyPress()

        While True
            Dim k As ConsoleKeyInfo = Console.ReadKey() 'read for a key press
            If Not k.Key.ToString.Length <= 0 Then 'check the length of the key string pressed, mainly because the key in this case will be apart of the system.consolekey
                Console.WriteLine(Environment.NewLine & "Key Pressed: " & k.Key.ToString) 'display the key pressed
                Second = 1 'restart the timer
            End If
        End While

    End Sub

End Module

Hope I helped!

Pantelis Natsiavas
  • 5,293
  • 5
  • 21
  • 36