Escuche la pulsación de tecla en la aplicación de consola .NET

¿Cómo puedo continuar ejecutando mi aplicación de consola hasta que se presione una tecla (como Esc se presiona?)

Supongo que está envuelto en un ciclo while. No me gusta ReadKey porque bloquea la operación y pide una clave, en lugar de simplemente continuar y escuchar la tecla presionada.

¿Cómo puede hacerse esto?

Use Console.KeyAvailable para que solo llame a ReadKey cuando sepa que no se bloqueará:

 Console.WriteLine("Press ESC to stop"); do { while (! Console.KeyAvailable) { // Do something } } while (Console.ReadKey(true).Key != ConsoleKey.Escape); 

Puede cambiar ligeramente su enfoque: use Console.ReadKey() para detener su aplicación, pero haga su trabajo en un hilo de fondo:

 static void Main(string[] args) { var myWorker = new MyWorker(); myWorker.DoStuff(); Console.WriteLine("Press any key to stop..."); Console.ReadKey(); } 

En la función myWorker.DoStuff() invocaría otra función en una myWorker.DoStuff() fondo (utilizando Action<>() o Func<>() es una manera fácil de hacerlo), y luego regresará inmediatamente.

La forma más corta:

 Console.WriteLine("Press ESC to stop"); while (!(Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape)) { // do something } 

Console.ReadKey() es una función de locking, detiene la ejecución del progtwig y espera a que se presione una tecla, pero gracias a la comprobación de Console.KeyAvailable primero, el ciclo while no se bloquea, sino que se ejecuta hasta que se presione Esc .

De la maldición del video Creación de aplicaciones .NET Console en C # por Jason Roberts en http://www.pluralsight.com

Podríamos hacer lo siguiente para tener múltiples procesos en ejecución

  static void Main(string[] args) { Console.CancelKeyPress += (sender, e) => { Console.WriteLine("Exiting..."); Environment.Exit(0); }; Console.WriteLine("Press ESC to Exit"); var taskKeys = new Task(ReadKeys); var taskProcessFiles = new Task(ProcessFiles); taskKeys.Start(); taskProcessFiles.Start(); var tasks = new[] { taskKeys }; Task.WaitAll(tasks); } private static void ProcessFiles() { var files = Enumerable.Range(1, 100).Select(n => "File" + n + ".txt"); var taskBusy = new Task(BusyIndicator); taskBusy.Start(); foreach (var file in files) { Thread.Sleep(1000); Console.WriteLine("Procesing file {0}", file); } } private static void BusyIndicator() { var busy = new ConsoleBusyIndicator(); busy.UpdateProgress(); } private static void ReadKeys() { ConsoleKeyInfo key = new ConsoleKeyInfo(); while (!Console.KeyAvailable && key.Key != ConsoleKey.Escape) { key = Console.ReadKey(true); switch (key.Key) { case ConsoleKey.UpArrow: Console.WriteLine("UpArrow was pressed"); break; case ConsoleKey.DownArrow: Console.WriteLine("DownArrow was pressed"); break; case ConsoleKey.RightArrow: Console.WriteLine("RightArrow was pressed"); break; case ConsoleKey.LeftArrow: Console.WriteLine("LeftArrow was pressed"); break; case ConsoleKey.Escape: break; default: if (Console.CapsLock && Console.NumberLock) { Console.WriteLine(key.KeyChar); } break; } } } } internal class ConsoleBusyIndicator { int _currentBusySymbol; public char[] BusySymbols { get; set; } public ConsoleBusyIndicator() { BusySymbols = new[] { '|', '/', '-', '\\' }; } public void UpdateProgress() { while (true) { Thread.Sleep(100); var originalX = Console.CursorLeft; var originalY = Console.CursorTop; Console.Write(BusySymbols[_currentBusySymbol]); _currentBusySymbol++; if (_currentBusySymbol == BusySymbols.Length) { _currentBusySymbol = 0; } Console.SetCursorPosition(originalX, originalY); } } 

Aquí hay un enfoque para que hagas algo en un hilo diferente y comiences a escuchar la tecla presionada en un hilo diferente. Y la consola detendrá su procesamiento cuando finalice su proceso real o el usuario finalice el proceso presionando la tecla Esc .

 class SplitAnalyser { public static bool stopProcessor = false; public static bool Terminate = false; static void Main(string[] args) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Split Analyser starts"); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Press Esc to quit....."); Thread MainThread = new Thread(new ThreadStart(startProcess)); Thread ConsoleKeyListener = new Thread(new ThreadStart(ListerKeyBoardEvent)); MainThread.Name = "Processor"; ConsoleKeyListener.Name = "KeyListener"; MainThread.Start(); ConsoleKeyListener.Start(); while (true) { if (Terminate) { Console.WriteLine("Terminating Process..."); MainThread.Abort(); ConsoleKeyListener.Abort(); Thread.Sleep(2000); Thread.CurrentThread.Abort(); return; } if (stopProcessor) { Console.WriteLine("Ending Process..."); MainThread.Abort(); ConsoleKeyListener.Abort(); Thread.Sleep(2000); Thread.CurrentThread.Abort(); return; } } } public static void ListerKeyBoardEvent() { do { if (Console.ReadKey(true).Key == ConsoleKey.Escape) { Terminate = true; } } while (true); } public static void startProcess() { int i = 0; while (true) { if (!stopProcessor && !Terminate) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Processing...." + i++); Thread.Sleep(3000); } if(i==10) stopProcessor = true; } } } 

Si está utilizando Visual Studio, puede usar “Iniciar sin depurar” en el menú Depurar.

Escribirá automáticamente “Presione cualquier tecla para continuar …” a la consola por usted una vez completada la aplicación y la dejará abierta hasta que se presione una tecla.

Abordando casos que algunas de las otras respuestas no manejan bien:

  • Sensible : ejecución directa del código de manejo de pulsación de tecla; evita los caprichos de la votación o el locking de retrasos
  • Opcionalidad : la pulsación de tecla global es opcional ; de lo contrario, la aplicación debería salir normalmente
  • Separación de preocupaciones : código de escucha menos invasivo; funciona independientemente del código de la aplicación de consola normal.

Muchas de las soluciones en esta página implican sondeo Console.KeyAvailable o locking en Console.ReadKey . Si bien es cierto que la .NET Console no es muy cooperativa aquí, puede usar Task.Run para avanzar hacia un modo de escucha Async más moderno.

El principal problema a tener en cuenta es que, de forma predeterminada, el subproceso de la consola no está configurado para la operación Async , lo que significa que, cuando se cae de la parte inferior de su función main , en lugar de esperar las terminaciones Async , su AppDoman y proceso terminará. Una forma adecuada de abordar esto sería utilizar el AsyncContext de Stephen Cleary para establecer soporte Async completo en su progtwig de consola de un único subproceso. Pero para casos más simples, como esperar una pulsación de tecla, instalar un trampolín completo puede ser excesivo.

El siguiente ejemplo sería para un progtwig de consola utilizado en algún tipo de archivo por lotes iterativo. En este caso, cuando el progtwig finaliza con su trabajo, normalmente debe salir sin requerir una pulsación de tecla, y luego permitimos presionar una tecla opcional para evitar que la aplicación salga. Podemos pausar el ciclo para examinar cosas, posiblemente reanudar, o usar la pausa como un conocido “punto de control” en el cual romper el archivo por lotes limpiamente.

 static void Main(String[] args) { Console.WriteLine("Press any key to prevent exit..."); var tHold = Task.Run(() => Console.ReadKey(true)); // ... do your console app activity ... if (tHold.IsCompleted) { #if false // For the 'hold' state, you can simply halt forever... Console.WriteLine("Holding."); Thread.Sleep(Timeout.Infinite); #else // ...or allow continuing to exit while (Console.KeyAvailable) Console.ReadKey(true); // flush/consume any extras Console.WriteLine("Holding. Press 'Esc' to exit."); while (Console.ReadKey(true).Key != ConsoleKey.Escape) ; #endif } } 

con el código siguiente, puede escuchar SpaceBar presionando, en el medio de la ejecución de la consola y pausar hasta que se presione otra tecla y también escuche EscapeKey para romper el bucle principal

 static ConsoleKeyInfo cki = new ConsoleKeyInfo(); while(true) { if (WaitOrBreak()) break; //your main code } private static bool WaitOrBreak(){ if (Console.KeyAvailable) cki = Console.ReadKey(true); if (cki.Key == ConsoleKey.Spacebar) { Console.Write("waiting.."); while (Console.KeyAvailable == false) { Thread.Sleep(250);Console.Write("."); } Console.WriteLine(); Console.ReadKey(true); cki = new ConsoleKeyInfo(); } if (cki.Key == ConsoleKey.Escape) return true; return false; } 

De acuerdo con mi experiencia, en las aplicaciones de consola la manera más fácil de leer la última tecla presionada es la siguiente (Ejemplo con las teclas de flecha):

 ConsoleKey readKey = Console.ReadKey ().Key; if (readKey == ConsoleKey.LeftArrow) {  (); //Do something } else if (readKey == ConsoleKey.RightArrow) {  (); //Do something } 

Utilizo para evitar bucles, en su lugar escribo el código de arriba dentro de un método, y lo llamo al final de “Método1” y “Método2”, entonces, después de ejecutar “Método1” o “Método2”, Console.ReadKey () .Key está listo para leer las claves nuevamente.