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

224

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

Supongo que está envuelto en un bucle while. No me gusta, ReadKeyya que bloquea la operación y pide una tecla, en lugar de continuar y escuchar la presión de la tecla.

¿Cómo se puede hacer esto?

karlstackoverflow
fuente

Respuestas:

343

Use Console.KeyAvailablepara que solo llame ReadKeycuando sepa que no bloqueará:

Console.WriteLine("Press ESC to stop");
do {
    while (! Console.KeyAvailable) {
        // Do something
   }       
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);
Jeff Sternal
fuente
66
pero si hace esto en lugar de readLine (), pierde la increíble característica de recuperar el historial presionando la tecla "arriba".
v.oddou
3
@ v.oddou Después de la última línea, simplemente agregue su Console.ReadLine()y ya está, ¿no?
Gaffi
1
¿Este bucle no consumirá mucha CPU / RAM? Si no, ¿cómo es eso?
Sofía
78

Puede cambiar su enfoque ligeramente: 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 myWorker.DoStuff()función, invocaría otra función en un subproceso en segundo plano (usando Action<>()o Func<>()es una forma fácil de hacerlo), y luego volverá inmediatamente.

slugster
fuente
60

El camino más corto:

Console.WriteLine("Press ESC to stop");

while (!(Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape))
{
    // do something
}

Console.ReadKey()es una función de bloqueo, detiene la ejecución del programa y espera la pulsación de una tecla, pero gracias a la comprobación Console.KeyAvailableinicial, el whilebucle no está bloqueado, sino que se ejecuta hasta que Escse presiona.

Yuliia Ashomok
fuente
El ejemplo más fácil hasta ahora. Gracias.
joelc
18

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

Podríamos hacer lo siguiente para tener un proceso de ejecución múltiple

  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);
        }
    }
alhpe
fuente
2
¿Puede explicar un poco sobre el enfoque que tenía? Estoy tratando de hacer lo mismo con mi aplicación de consola. Una explicación para el código sería genial.
nayef harb
@nayefharb configura varias tareas, las inicia y WaitAll esperará hasta que todas hayan regresado. Por lo tanto, las tareas individuales no volverán y continuará para siempre hasta que se active CancelKeyPress.
wonea 02 de
Console.CursorVisible = false;Será mejor evitar los parpadeos del cursor. Agregue esta línea como primera línea en el static void Main(string[] args)bloque.
Hitesh
12

Aquí hay un enfoque para que usted haga algo en un hilo diferente y comience 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 Esctecla.

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;

        }
    }

}
waheed
fuente
5

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

Se escribirá automáticamente "Presione cualquier tecla para continuar..." a la consola para usted al completar la aplicación y dejará la consola abierta para usted hasta que se presione una tecla.

LVBen
fuente
3

Abordar 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 las encuestas o retrasos en el bloqueo
  • 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.KeyAvailableo bloqueo Console.ReadKey. Si bien es cierto que .NET Consoleno es muy cooperativo aquí, puede usarlo Task.Runpara avanzar hacia un Asyncmodo de escucha más moderno .

El problema principal a tener en cuenta es que, de manera predeterminada, el subproceso de la consola no está configurado para Asyncfuncionar, lo que significa que, cuando se cae del fondo de su mainfunción, en lugar de esperar la Asyncfinalización, su AppDoman y el proceso finalizarán . Una forma adecuada de abordar esto sería utilizar el AsyncContext de Stephen Cleary para establecer un Asyncsoporte completo en su programa de consola de un solo 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 programa de consola utilizado en algún tipo de archivo por lotes iterativo. En este caso, cuando el programa termina con su trabajo, normalmente debería salir sin necesidad de presionar una 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 'punto de control' conocido en el que se puede salir limpiamente del archivo por lotes.

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
    }
}
Glenn Slayden
fuente
1

con el siguiente código, puede escuchar la barra espaciadora presionando, en medio de la ejecución de su consola y pausar hasta que se presione otra tecla y también escuchar 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;
    }
Iman
fuente
-1

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

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

Yo uso para evitar bucles, en su lugar escribo el código anterior dentro de un método, y lo llamo al final de "Método1" y "Método2", por lo tanto, después de ejecutar "Método1" o "Método2", Console.ReadKey () .Key está listo para leer las claves nuevamente.

Dhemmlerf
fuente