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

Posted

技术标签:

【中文标题】在 .NET 控制台应用程序中监听按键【英文标题】:Listen for key press in .NET console app 【发布时间】:2011-08-18 23:47:32 【问题描述】:

我如何才能继续运行我的控制台应用程序,直到按键(如 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);

【讨论】:

但是如果你这样做而不是 readLine() 你会失去通过按“向上”键来回忆历史的真棒功能。 @v.oddou 在最后一行之后,只需添加您的Console.ReadLine() 就可以了,不是吗? 这个循环不会消耗大量的 CPU/RAM 吗?如果不是,那怎么办?【参考方案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.KeyAvailablewhile 循环没有被阻塞,而是一直运行到 Esc 被按下。

【讨论】:

【参考方案4】:

来自 Jason Roberts http://www.pluralsight.comhttp://www.pluralsight.com 的视频 curse 用 C# 构建 .NET 控制台应用程序

我们可以执行以下操作来拥有多个运行进程

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

【讨论】:

你能解释一下我试图对我的控制台应用程序做同样事情的方法吗?对代码的解释会很好。 @nayefharb 设置了一堆任务,启动它们,WaitAll 将等待它们全部返回。因此,单个任务不会返回,它将永远持续下去,直到触发 CancelKeyPress。 Console.CursorVisible = false; 最好避免光标闪烁故障。将此行添加为static void Main(string[] args) 块中的第一行。 var tasks = new[] taskKeys ; 应该是var tasks = new[] taskKeys, taskProcessFiles ;【参考方案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】:

使用以下代码,您可以在控制台执行过程中侦听空格键并暂停,直到按下另一个键,另外还可以选择侦听 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;

【讨论】:

【参考方案9】:

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

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

我用来避免循环,而是在方法中编写上面的代码,并在“Method1”和“Method2”的末尾调用它,因此,在执行“Method1”或“Method2”之后,控制台。 ReadKey().Key 已准备好再次读取密钥。

【讨论】:

以上是关于在 .NET 控制台应用程序中监听按键的主要内容,如果未能解决你的问题,请参考以下文章

c语言怎么监听键盘按键

“监听”媒体按键事件

简单的按键不会触发任何监听器

ReactVR 全局按键事件监听器

Flutter - 监听硬件按键。防止默认行为,android

Java中的事件监听器没有应用程序有焦点? (全局按键检测)