ReadKey async console or callback?

I am trying to press Q to exit the console window. I do not like my current implementation. Is there a way that I can asynchronously or use a callback to get keys from the console?

+4
source share
5 answers

You can call Console.ReadKey() from another thread so that it does not block your main thread. (You can use .Net 4 Task or the old Thread to start a new thread.)

 class Program { static volatile bool exit = false; static void Main() { Task.Factory.StartNew(() => { while (Console.ReadKey().Key != ConsoleKey.Q) ; exit = true; }); while (!exit) { // Do stuff } } } 
+12
source

You can use the KeyAvailable property (Framework 2.0):

 if (System.Console.KeyAvailable) { ConsoleKeyInfo key = System.Console.ReadKey(true);//true don't print char on console if (key.Key == ConsoleKey.Q) { //Do something } } 
+4
source

I did not find any of the existing answers completely satisfactory, so I wrote my own to work with TAP and .Net 4.5.

 /// <summary> /// Obtains the next character or function key pressed by the user /// asynchronously. The pressed key is displayed in the console window. /// </summary> /// <param name="cancellationToken"> /// The cancellation token that can be used to cancel the read. /// </param> /// <param name="responsiveness"> /// The number of milliseconds to wait between polling the /// <see cref="Console.KeyAvailable"/> property. /// </param> /// <returns>Information describing what key was pressed.</returns> /// <exception cref="TaskCanceledException"> /// Thrown when the read is cancelled by the user input (Ctrl+C etc.) /// or when cancellation is signalled via /// the passed <paramred name="cancellationToken"/>. /// </exception> public static async Task<ConsoleKeyInfo> ReadKeyAsync( CancellationToken cancellationToken, int responsiveness = 100) { var cancelPressed = false; var cancelWatcher = new ConsoleCancelEventHandler( (sender, args) => { cancelPressed = true; }); Console.CancelKeyPress += cancelWatcher; try { while (!cancelPressed && !cancellationToken.IsCancellationRequested) { if (Console.KeyAvailable) { return Console.ReadKey(); } await Task.Delay( responsiveness, cancellationToken); } if (cancelPressed) { throw new TaskCanceledException( "Readkey canceled by user input."); } throw new TaskCanceledException(); } finally { Console.CancelKeyPress -= cancelWatcher; } } 
+4
source

Taking from all the answers here, this is my version:

 public class KeyHandler { public event EventHandler KeyEvent; public void WaitForExit() { bool exit = false; do { var key = Console.ReadKey(true); //blocks until key event switch (key.Key) { case ConsoleKey.Q: exit = true; break; case ConsoleKey.T: // raise a custom event eg: Increase throttle break; } } while (!exit); } } static void Main(string[] args) { var worker = new MyEventDrivenClassThatDoesCoolStuffByItself(); worker.Start(); var keyHandler = new KeyHandler(); keyHandler.KeyEvent+= keyHandler_KeyEvent; // modify properties of your worker keyHandler.WaitForExit(); } 
  • This does not require Main to do anything in the loop, allowing it to simply organize the interaction between the control keys and manipulate the properties of the working class.
  • Accepting the @Hans hint, KeyHandler does not need to asynchronize the new thread, since Console.ReadKey is blocked until the key is received.
0
source

Here is the implementation I created using KeyAvailable . This leads to a prompt at the bottom of the console window, and everything that is printed on the console starts at the top.

 public class Program { private static int consoleLine; private static int consolePromptLine; private static bool exit; static string clearLine = new string(' ', Console.BufferWidth - 1); public static void Main(string[] args) { StringBuilder commandCapture = new StringBuilder(10); string promptArea = "Command> "; consolePromptLine = Console.WindowTop + Console.WindowHeight - 1; ClearLine(consolePromptLine); Console.Write(promptArea); while (!exit) { // Do other stuff // Process input if (Console.KeyAvailable) { var character = Console.ReadKey(true); if (character.Key == ConsoleKey.Enter) { if (commandCapture.Length != 0) { ProcessCommand(commandCapture.ToString()); commandCapture.Clear(); ClearLine(consolePromptLine); Console.Write(promptArea); } } else { if (character.Key == ConsoleKey.Backspace) { if (commandCapture.Length != 0) { commandCapture.Remove(commandCapture.Length - 1, 1); ClearLine(consolePromptLine); Console.Write(promptArea); Console.Write(commandCapture.ToString()); } } else { commandCapture.Append(character.KeyChar); Console.SetCursorPosition(0, consolePromptLine); Console.Write(promptArea); Console.Write(commandCapture.ToString()); } } } } } private static void ProcessCommand(string command) { if (command == "start") { Task<string> testTask = new Task<string>(() => { System.Threading.Thread.Sleep(4000); return "Test Complete"; }); testTask.ContinueWith((t) => { Print(t.Result); }, TaskContinuationOptions.ExecuteSynchronously); testTask.Start(); } else if (command == "quit") { exit = true; } Print(command); consolePromptLine = Console.WindowTop + Console.WindowHeight - 1; } public static void Print(string text) { ClearLine(consoleLine); Console.WriteLine(text); consoleLine = Console.CursorTop; } public static void ClearLine(int line) { Console.SetCursorPosition(0, line); Console.Write(clearLine); Console.SetCursorPosition(0, line); } } 
0
source

All Articles