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ć?

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

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.


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.


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

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;

        }
    }

}

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.


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

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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.