Message pump in a console application

2019-02-22 02:54发布

问题:

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).

回答1:

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.



回答2:

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!