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

我如何继续运行我的控制台应用程序,直到按下一个键(如Esc按下?)

我假设它环绕着一个while循环。我不喜欢ReadKey,因为它阻塞操作并要求一个键,而不是只是继续并听按键。

如何做到这一点呢?

350911 次浏览

你可以稍微改变一下你的方法——使用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.KeyAvailable,这样你只会在知道ReadKey不会阻塞时调用它:

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

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


}
}


}

最短的方法:

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


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

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

如果你正在使用Visual Studio,那么你可以在调试菜单中使用“开始不调试”。

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

从视频诅咒构建。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);
}
}

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

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()。键已准备好再次读取键。

处理一些其他答案不能很好处理的情况:

  • 响应:直接执行按键处理代码;避免轮询或阻塞延迟的变化
  • 可选性:全局按键为可选;否则应用程序应该正常退出
  • 分离关注点:低侵入性监听代码;独立于正常的控制台应用程序代码运行。

本页上的许多解决方案都涉及轮询Console.KeyAvailable或阻塞Console.ReadKey。虽然。net Console在这里确实不是很合作,但你可以使用Task.Run来转向更现代的Async监听模式。

需要注意的主要问题是,默认情况下,你的控制台线程没有为Async操作设置——这意味着,当你从main函数的底部掉出来时,而不是等待Async完成,你的AppDoman和进程将结束。解决这个问题的正确方法是使用Stephen Cleary的AsyncContext在你的单线程控制台程序中建立完全的Async支持。但对于更简单的情况,比如等待按键,安装完整的蹦床可能是多余的。

下面的例子是用于某种迭代批处理文件的控制台程序。在这种情况下,当程序完成它的工作时,通常它应该退出没有,需要一个按键,然后我们允许一个可选的按键来防止应用程序退出。我们可以暂停循环来检查内容,也可以继续,或者将暂停作为一个已知的“控制点”,在这个控制点上干净地跳出批处理文件。

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

使用以下代码,您可以在控制台执行过程中监听空格键,并暂停,直到按下另一个键,并选择监听Escape键,以打破主循环。

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;
}
Console.WriteLine("Hello");
var key = Console.ReadKey();
DateTime start = DateTime.Now;
bool gotKey = Console.KeyAvailable;


while ((DateTime.Now - start).TotalSeconds < 2)
{
if (key.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
else if (key.Key == ConsoleKey.Enter)
{
break;
}