Слушайте нажатие клавиши в приложении консоли .NET.

Как я могу продолжать запускать консольное приложение до тех пор, пока не будет нажата клавиша (например, Esc?)

Я предполагаю, что он обернут цикл while. Мне не нравится ReadKey, поскольку он блокирует операцию и запрашивает ключ, а не просто продолжает и слушает нажатие клавиши.

Как это можно сделать?

Ответ 1

Используйте Console.KeyAvailable, чтобы вы вызывали только ReadKey, когда знаете, что он не будет блокироваться:

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

Ответ 2

Вы можете немного изменить свой подход - используйте Console.ReadKey(), чтобы остановить приложение, но выполните свою работу в фоновом потоке:

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

В функции myWorker.DoStuff() вы затем вызываете другую функцию в фоновом потоке (используя Action<>() или Func<>() - это простой способ сделать это), а затем сразу же возвратитесь.

Ответ 3

Самый короткий путь:

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

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

Console.ReadKey() является блокирующей функцией, она останавливает выполнение программы и ждет нажатия клавиши, но благодаря проверке Console.KeyAvailable сначала цикл while не блокируется, а работает до Esc.

Ответ 4

Из прокрутки видео Создание приложений .NET Console в С# Джейсона Робертса в http://www.pluralsight.com

Мы можем сделать следующее, чтобы иметь несколько запущенных процессов

  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);
        }
    }

Ответ 5

Здесь вы можете сделать что-то в другом потоке и начать слушать нажатие клавиши в другом потоке. Консоль прекратит свою обработку, когда ваш фактический процесс завершится, или пользователь завершит процесс нажатием клавиши 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;

        }
    }

}

Ответ 6

Если вы используете Visual Studio, вы можете использовать "Начать без отладки" в меню "Отладка".

Он автоматически напишет "Нажмите любую клавишу, чтобы продолжить...". на консоль для вас по завершении приложения, и он оставит консоль открытой для вас, пока не будет нажата клавиша.

Ответ 7

Адресация случаев, когда некоторые другие ответы плохо обрабатываются:

  • Отзывчивый: прямое выполнение кода обработки нажатия клавиши; избегает капризов опроса или блокировки задержки
  • Опциональность: глобальное нажатие кнопки - это выбор; в противном случае приложение должно нормально выйти
  • Разделение проблем: менее инвазивный код прослушивания; работает независимо от обычного консольного приложения.

Многие из решений на этой странице включают опрос Console.KeyAvailable или блокировку на Console.ReadKey. Хотя верно, что .NET Console здесь не очень кооператив, вы можете использовать Task.Run для перехода к более современному способу прослушивания Async.

Основная проблема, о которой следует помнить, заключается в том, что по умолчанию ваш консольный поток не настроен для работы Async - это означает, что когда вы выходите из нижней части своей функции main, вместо ожидая завершения Async, ваш AppDoman и процесс завершатся. Правильным способом решения этой проблемы было бы использовать Stephen Cleary AsyncContext, чтобы установить полную поддержку Async в вашей однопоточной консольной программе. Но для более простых случаев, таких как ожидание нажатия клавиши, установка полного trampoline может быть чрезмерной.

Пример ниже будет для консольной программы, используемой в каком-то итеративном пакетном файле. В этом случае, когда программа выполняется с ее работой, обычно она должна выйти из без, требующей нажатия клавиши, а затем мы позволяем необязательному нажатию клавиши предотвращать приложение от выхода, Мы можем приостановить цикл, чтобы исследовать вещи, возможно, возобновить или использовать паузу как известную "контрольную точку", в которой можно полностью вырваться из командного файла.

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
    }
}

Ответ 8

По моему опыту, в консольных приложениях самый простой способ прочитать последнюю нажатую клавишу выглядит следующим образом (Пример со стрелками):

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

Я использую, чтобы избежать циклов, вместо этого я пишу код выше в методе, и я вызываю его в конце как "Method1", так и "Method2", поэтому после выполнения "Method1" или "Method2", Console. ReadKey(). Ключ готов снова прочитать ключи.