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

kar*_*low 206 c# console-application

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

我假设它缠绕了一圈while循环.我不喜欢ReadKey它,因为它阻止操作并要求一个键,而不是只是继续并听取按键.

如何才能做到这一点?

Jef*_*nal 318

使用,Console.KeyAvailable以便您只ReadKey知道它不会阻止:

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

  • 这个循环不会消耗大量的CPU/RAM吗?如果不是,怎么会这样呢? (8认同)
  • 但如果你这样做而不是readLine(),你会失去通过按"向上"键进行历史记录回忆的强大功能. (6认同)
  • @ v.oddou在最后一行之后,只需添加你的`Console.ReadLine()`然后你就设置了,不是吗? (3认同)

slu*_*ter 75

您可以稍微改变您的方法 - 用于Console.ReadKey()停止您的应用程序,但在后台线程中完成您的工作:

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

myWorker.DoStuff()函数中,您将在后台线程上调用另一个函数(使用Action<>()或是Func<>()一种简单的方法),然后立即返回.


Yul*_*mok 54

最短的方式:

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

while (!(Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape))
{
    // do something
}
Run Code Online (Sandbox Code Playgroud)

Console.ReadKey()是一个阻塞功能,它停止程序的执行并等待按键,但是由于首先检查Console.KeyAvailable,while循环没有被阻止,而是一直运行直到Esc被按下.


alh*_*hpe 16

来自Jason Roberts的视频诅咒在C#中构建.NET控制台应用程序,网址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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 你能解释一下我试图用我的控制台应用程序做同样的方法.对代码的解释会很棒. (2认同)

小智 12

这是一种方法,您可以在不同的线程上执行某些操作,并开始侦听在不同线程中按下的键.当您的实际流程结束或用户通过按键终止流程时,控制台将停止处理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;

        }
    }

}
Run Code Online (Sandbox Code Playgroud)


Gle*_*den 6

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

  • 响应式:显式/直接执行按键处理代码;避免轮询或阻塞延迟的变幻莫测
  • 可选性:全局按键是可选的;默认情况下,应用程序正常退出(如果没有任何操作)
  • 关注点分离:侵入性较小的监听代码;独立于控制台应用程序的核心逻辑运行。

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

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

下面的示例适用于某种迭代批处理文件中使用的控制台程序。在这种情况下,当程序完成工作时,通常它应该退出而不需要按键,然后我们允许可选的按键来阻止应用程序退出。我们可以暂停循环来检查事情,可能会恢复,或者使用暂停作为已知的“控制点”,在该点上干净地打破批处理文件。

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 on (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
    }
}
Run Code Online (Sandbox Code Playgroud)


LVB*_*Ben 5

如果使用的是Visual Studio,则可以在“调试”菜单中使用“不调试就开始”。

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