在.NET控制台应用程序中听按键

我怎样才能继续运行我的控制台应用程序,直到按键(如Esc被按下?)

我假设它包裹了一段时间循环。 我不喜欢ReadKey因为它阻止了操作,并要求一个键,而不是继续听按键。

如何才能做到这一点?

使用Console.KeyAvailable以便只在您知道不会阻止的情况下调用ReadKey

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

您可以稍微改变您的方法 – 使用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<>()是一个简单的方法),然后立即返回。

最短的方法:

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

Console.ReadKey()是一个阻塞函数,它会停止程序的执行并等待按下按键,但是首先检查Console.KeyAvailablewhile循环没有被阻塞,而是一直运行,直到按Esc键。

从video诅咒build设.NET控制台应用程序在C#由杰森罗伯茨在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); } } 

这里有一个方法可以让你在不同的线程上执行某些操作,并开始监听不同线程中按下的按键。 当您的实际过程结束或用户通过按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; } } } 

如果您正在使用Visual Studio,则可以使用“debugging”菜单中的“无需debugging即可开始”。

它会自动写入“按任意键继续…” 在完成应用程序后,将会为您打开控制台,直到按下某个键为止。

解决一些其他答案处理不好的情况:

  • 响应 :直接执行按键处理代码; 避免了轮询或阻碍延迟的变幻莫测
  • 可选性 :全球按键是select性的 ; 否则应用程序应该正常退出
  • 分离问题 :侵入性较弱的听力代码; 独立于正常的控制台应用代码运行。

此页面上的许多解决scheme都涉及到轮询Console.KeyAvailable或在Console.ReadKey阻止。 尽pipe.NET Console在这里并不是很合作,但是您可以使用Task.Run转向更现代的Async模式。

要注意的主要问题是,默认情况下,您的控制台线程没有设置为Async操作 – 这意味着,当你掉在你的mainfunction的底部,而不是等待Async完成,你的AppDoman和过程将结束。 解决这个问题的正确方法是使用Stephen Cleary的AsyncContext在单线程控制台程序中build立完整的Async支持。 但对于更简单的情况,如等待按键,安装一个完整的蹦床可能是矫枉过正。

下面的例子是用于某种迭代batch file中的控制台程序。 在这种情况下,当程序完成其工作时,通常应该退出而不需要按键,然后我们允许可选的按键来防止应用程序退出。 我们可以暂停循环来检查事情,可能会恢复,或者使用暂停作为一个已知的“控制点”,从而彻底地打破batch file。

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

根据我的经验,在控制台应用程序中,读取最后一个按键的最简单的方法如下(用箭头键示例):

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

我使用避免循环,而不是我写上面的代码在一个方法,我称之为“方法1”和“方法2”的末尾,所以,执行“方法1”或“Method2”后,Console.ReadKey() .Key已经准备好再次读取密钥。