Słuchaj naciskania klawiszy w aplikacji konsoli .NET

224

Jak mogę nadal uruchamiać aplikację konsoli, dopóki nie zostanie naciśnięty klawisz (jak Esczostanie naciśnięty?)

Zakładam, że jest owinięty wokół pętli while. Nie podoba mi się, ReadKeyponieważ blokuje działanie i prosi o klawisz, zamiast po prostu kontynuować i słuchać naciśnięcia klawisza.

Jak można to zrobić?

karlstackoverflow
źródło

Odpowiedzi:

343

Użyj Console.KeyAvailable, aby zadzwonić tylko ReadKeywtedy, gdy wiesz, że nie zablokuje:

Console.WriteLine("Press ESC to stop");
do {
    while (! Console.KeyAvailable) {
        // Do something
   }       
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);
Jeff Sternal
źródło
6
ale jeśli zrobisz to zamiast readLine (), stracisz niesamowitą funkcję przywracania historii, naciskając klawisz „w górę”.
v.oddou
3
@ v.oddou Po ostatniej linii, po prostu dodaj swoją Console.ReadLine()i gotowe, nie?
Gaffi
1
Czy ta pętla nie zużywa dużo procesora / pamięci RAM? Jeśli nie, to w jaki sposób?
Sofia,
78

Możesz nieco zmienić swoje podejście - użyj, Console.ReadKey()aby zatrzymać aplikację, ale wykonuj swoją pracę w tle:

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

W myWorker.DoStuff()funkcji wywołujesz inną funkcję w wątku w tle (używając Action<>()lub Func<>()jest to łatwy sposób, aby to zrobić), a następnie natychmiast powracasz.

ślimak
źródło
60

Najkrótsza droga:

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

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

Console.ReadKey()jest funkcją blokującą, zatrzymuje wykonywanie programu i czeka na naciśnięcie klawisza, ale dzięki sprawdzeniu Console.KeyAvailablenajpierw whilepętla nie jest blokowana, ale działa do momentu Escnaciśnięcia.

Julia Ashomok
źródło
Najłatwiejszy jak dotąd przykład. Dziękuję Ci.
joelc
18

Z klątwy wideo Budowanie aplikacji konsoli .NET w języku C # przez Jasona Robertsa na stronie http://www.pluralsight.com

Możemy wykonać następujące czynności, aby uruchomić wiele procesów

  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
źródło
2
Czy możesz wyjaśnić trochę podejście, które miałeś, staram się zrobić to samo z moją aplikacją konsolową. Wyjaśnienie kodu byłoby świetne.
nayef harb
@nayefharb skonfiguruje kilka zadań, uruchom je, a WaitAll będzie czekać, aż wszystkie zostaną zwrócone. Dlatego poszczególne zadania nie powrócą i będą trwać wiecznie, dopóki nie zostanie uruchomione CancelKeyPress.
wonea
Console.CursorVisible = false;najlepiej będzie uniknąć migania kursora. Dodaj tę linię jako pierwszą linię w static void Main(string[] args)bloku.
Hitesh
12

Oto podejście, aby zrobić coś z innego wątku i zacząć słuchać klawisza wciśniętego w innym wątku. Konsola przerwie przetwarzanie, gdy zakończy się faktyczny proces lub użytkownik zakończy proces, naciskając Escklawisz.

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
źródło
5

Jeśli korzystasz z programu Visual Studio, możesz użyć opcji „Rozpocznij bez debugowania” w menu debugowania.

Automatycznie napisze „Naciśnij dowolny klawisz, aby kontynuować.” do konsoli po zakończeniu aplikacji i pozostawi konsolę otwartą do momentu naciśnięcia klawisza.

LVBen
źródło
3

Rozwiązanie problemów, z którymi niektóre inne odpowiedzi nie radzą sobie dobrze:

  • Responsive : bezpośrednie wykonanie kodu obsługi naciśnięcia klawisza; pozwala uniknąć kaprysów opóźnień związanych z odpytywaniem lub blokowaniem
  • Opcjonalnie : globalne naciśnięcie klawisza jest opcjonalne ; w przeciwnym razie aplikacja powinna zakończyć się normalnie
  • Rozdzielenie obaw : mniej inwazyjny kod nasłuchu; działa niezależnie od normalnego kodu aplikacji konsoli.

Wiele rozwiązań na tej stronie wymaga odpytywania Console.KeyAvailablelub blokowania Console.ReadKey. Chociaż prawdą jest, że .NET Consolenie jest tu zbyt kooperatywny, możesz Task.Runprzejść do bardziej nowoczesnego Asynctrybu słuchania.

Głównym problemem, o którym należy pamiętać, jest to, że domyślnie wątek konsoli nie jest skonfigurowany do Asyncdziałania - co oznacza, że ​​gdy wypadniesz z dołu mainfunkcji, zamiast Asyncczekać na zakończenie, twoja aplikacja i proces zakończą się . Właściwym sposobem rozwiązania tego problemu byłoby użycie AsyncContext Stephena Cleary'ego do ustanowienia pełnego Asyncwsparcia w jednowątkowym programie konsoli. Ale w prostszych przypadkach, takich jak oczekiwanie na naciśnięcie klawisza, zainstalowanie pełnej trampoliny może być przesadą.

Poniższy przykład dotyczy programu konsoli używanego w pewnego rodzaju iteracyjnym pliku wsadowym. W takim przypadku, gdy program zakończy pracę, zwykle powinien zakończyć działanie bez konieczności naciskania klawisza, a następnie zezwalamy na opcjonalne naciśnięcie klawisza, aby uniemożliwić zamknięcie aplikacji. Możemy wstrzymać cykl w celu zbadania rzeczy, być może wznowić, lub użyć pauzy jako znanego „punktu kontrolnego”, w którym można czysto wyrwać się z pliku wsadowego.

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
źródło
1

z poniższym kodem możesz słuchać wciskania klawisza SpaceBar, w trakcie wykonywania konsoli i wstrzymywać się do momentu naciśnięcia innego klawisza, a także nasłuchiwać EscapeKey za zerwanie głównej pętli

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
źródło
-1

Z mojego doświadczenia wynika, że ​​w aplikacjach konsolowych najłatwiejszy sposób odczytania ostatniego naciśniętego klawisza jest następujący (przykład z klawiszami strzałek):

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

Używam, aby unikać pętli, zamiast tego piszę powyższy kod w metodzie i wywołuję go na końcu zarówno „Method1”, jak i „Method2”, więc po wykonaniu „Method1” lub „Method2” Console.ReadKey () Klawisz jest gotowy do ponownego odczytania kluczy.

Dhemmlerf
źródło