Консоль ReadKey асинхронная или обратный вызов?

Я пытаюсь нажать Q, чтобы выйти из окна консоли. Мне не нравится моя текущая реализация. Есть ли способ асинхронизировать или использовать обратный вызов для получения ключей с консоли?


person Community    schedule 01.08.2010    source источник


Ответы (6)


Вы можете вызвать Console.ReadKey() из другого потока, чтобы он не блокировал ваш основной поток. (Вы можете использовать .Net 4 Task или старый Thread, чтобы начать новый поток.)

class Program
{
    static volatile bool exit = false;

    static void Main()
    {
        Task.Factory.StartNew(() =>
            {
                while (Console.ReadKey().Key != ConsoleKey.Q) ;
                exit = true;
            });

        while (!exit)
        {
            // Do stuff
        }
    }
}
person svick    schedule 01.08.2010
comment
На самом деле это то, что я делаю (ну, часть readkey в потоке сравнения). Эта фабрика задач выглядит круто. Проблема с моим кодом прямо сейчас заключается в том, что .ReadKey, кажется, не возвращается, пока я не переведу окно в фокус. Поэтому мне не нравится мое решение. Когда основной поток заканчивается, другой поток все еще заблокирован. Ваше решение выглядит круто, возможно, потоки задач умрут, когда основной. В этом случае это должно работать, однако банкомат. Это приложение только для версии 3.5. - person ; 01.08.2010
comment
Если вы хотите, чтобы Thread останавливался после завершения основного потока, просто установите Thread.IsBackground в true. - person svick; 01.08.2010
comment
И приведенный выше код, и IsBackground решают мою проблему. +1 - person ; 01.08.2010
comment
С подходом задачи вы можете создать новый CancellationTokenSource, передать свойство Token любым задачам, которые вы хотите убить, а затем вызвать CancellationTokenSource.Cancel(), чтобы убить их. Я уверен, что все знают об этом спустя 8 лет, лол. - person Seth; 09.01.2019

Я не нашел ни один из существующих ответов полностью удовлетворительным, поэтому я написал свой собственный, чтобы работать с TAP и .Net 4.5.

/// <summary>
/// Obtains the next character or function key pressed by the user
/// asynchronously. The pressed key is displayed in the console window.
/// </summary>
/// <param name="cancellationToken">
/// The cancellation token that can be used to cancel the read.
/// </param>
/// <param name="responsiveness">
/// The number of milliseconds to wait between polling the
/// <see cref="Console.KeyAvailable"/> property.
/// </param>
/// <returns>Information describing what key was pressed.</returns>
/// <exception cref="TaskCanceledException">
/// Thrown when the read is cancelled by the user input (Ctrl+C etc.)
/// or when cancellation is signalled via
/// the passed <paramred name="cancellationToken"/>.
/// </exception>
public static async Task<ConsoleKeyInfo> ReadKeyAsync(
    CancellationToken cancellationToken,
    int responsiveness = 100)
{
    var cancelPressed = false;
    var cancelWatcher = new ConsoleCancelEventHandler(
        (sender, args) => { cancelPressed = true; });
    Console.CancelKeyPress += cancelWatcher;
    try
    {
        while (!cancelPressed && !cancellationToken.IsCancellationRequested)
        {
            if (Console.KeyAvailable)
            {
                return Console.ReadKey();
            }

            await Task.Delay(
                responsiveness,
                cancellationToken);
        }

        if (cancelPressed)
        {
            throw new TaskCanceledException(
                "Readkey canceled by user input.");
        }

        throw new TaskCanceledException();
    }
    finally
    {
        Console.CancelKeyPress -= cancelWatcher;
    }
}
person Jodrell    schedule 13.05.2014

Вы можете использовать свойство KeyAvailable (Framework 2.0):

if (System.Console.KeyAvailable)
{
   ConsoleKeyInfo key = System.Console.ReadKey(true);//true don't print char on console
   if (key.Key == ConsoleKey.Q)
   {
       //Do something
   }
}
person user2794831    schedule 19.09.2013

Вот как я это сделал:

// Comments language: pt-BR
// Aguarda key no console
private static async Task<ConsoleKey> WaitConsoleKey ( ) {
    try {
        // Prepara retorno
        ConsoleKey key = default;
        // Aguarda uma tecla ser pressionada
        await Task.Run ( ( ) => key = Console.ReadKey ( true ).Key );
        // Retorna a tecla
        return key;
    }
    catch ( Exception ex ) {
        throw ex;
    }
}
person Rechdan    schedule 25.06.2018
comment
Хороший. Это лучшее решение на странице с точки зрения точного соблюдения современной async чувствительности. Например, важно отметить, что Task.Run(…) обладает специальными встроенными знаниями о современных облегченных async методах, сгенерированных компилятором, что позволяет отличать их от устаревших ThreadPool- или TaskContinuationSource-поддерживаемых Task-методов, возвращающих Task. Это различие важно для поддержания жизни Console приложений; менее сложный код преждевременно завершает работу по (мнимому) завершению продолжения Task, возвращаемого методом async верхнего уровня. - person Glenn Slayden; 28.09.2019
comment
Почему попытка поймать с повторным вызовом того же исключения? - person John Hamm; 12.05.2020
comment
@JohnHamm это потому, что в то время у меня там был журнал, поэтому я вынул его и заменил этим броском, но это полностью устранимо. ???? - person Rechdan; 05.07.2020

Исходя из всех ответов здесь, это моя версия:

public class KeyHandler
{
    public event EventHandler KeyEvent;

    public void WaitForExit()
    {
        bool exit = false;
        do
        {
            var key = Console.ReadKey(true); //blocks until key event
            switch (key.Key)
            {
                case ConsoleKey.Q:
                    exit = true;
                    break;
               case ConsoleKey.T:
                    // raise a custom event eg: Increase throttle
                    break;
            }
        }
        while (!exit);
    }
}


static void Main(string[] args)
{
    var worker = new MyEventDrivenClassThatDoesCoolStuffByItself();
    worker.Start();

    var keyHandler = new KeyHandler();
    keyHandler.KeyEvent+= keyHandler_KeyEvent; // modify properties of your worker
    keyHandler.WaitForExit();
}
  • Он не требует, чтобы Main выполнял какие-либо действия в цикле, позволяя ему просто координировать обработку ключей и манипулирование свойствами рабочего класса.
  • Принимая подсказку от @Hans, KeyHandler не нужно асинхронизировать новый поток, поскольку Console.ReadKey блокируется до тех пор, пока не будет получен ключ.
person fiat    schedule 14.09.2014

Вот реализация, которую я создал с помощью KeyAvailable. При этом подсказка остается в нижней части окна консоли, в то время как все, что «выводится» на консоль, начинается сверху.

public class Program
{
    private static int consoleLine;
    private static int consolePromptLine;
    private static bool exit;
    static string clearLine = new string(' ', Console.BufferWidth - 1);

    public static void Main(string[] args)
    {
        StringBuilder commandCapture = new StringBuilder(10);
        string promptArea = "Command> ";

        consolePromptLine = Console.WindowTop + Console.WindowHeight - 1;

        ClearLine(consolePromptLine);
        Console.Write(promptArea);

        while (!exit)
        {
            // Do other stuff

            // Process input
            if (Console.KeyAvailable)
            {
                var character = Console.ReadKey(true);

                if (character.Key == ConsoleKey.Enter)
                {
                    if (commandCapture.Length != 0)
                    {
                        ProcessCommand(commandCapture.ToString());
                        commandCapture.Clear();
                        ClearLine(consolePromptLine);
                        Console.Write(promptArea);
                    }
                }
                else
                {
                    if (character.Key == ConsoleKey.Backspace)
                    {
                        if (commandCapture.Length != 0)
                        {
                            commandCapture.Remove(commandCapture.Length - 1, 1);
                            ClearLine(consolePromptLine);
                            Console.Write(promptArea);
                            Console.Write(commandCapture.ToString());
                        }
                    }
                    else
                    {
                        commandCapture.Append(character.KeyChar);
                        Console.SetCursorPosition(0, consolePromptLine);
                        Console.Write(promptArea);
                        Console.Write(commandCapture.ToString());
                    }
                }
            }
        }

    }

    private static void ProcessCommand(string command)
    {
        if (command == "start")
        {
            Task<string> testTask = new Task<string>(() => { System.Threading.Thread.Sleep(4000); return "Test Complete"; });

            testTask.ContinueWith((t) => { Print(t.Result); }, TaskContinuationOptions.ExecuteSynchronously);
            testTask.Start();
        }
        else if (command == "quit")
        {
            exit = true;
        }

        Print(command);
        consolePromptLine = Console.WindowTop + Console.WindowHeight - 1;
    }

    public static void Print(string text)
    {
        ClearLine(consoleLine);
        Console.WriteLine(text);
        consoleLine = Console.CursorTop;
    }

    public static void ClearLine(int line)
    {
        Console.SetCursorPosition(0, line);
        Console.Write(clearLine);
        Console.SetCursorPosition(0, line);
    }
}
person Thraka    schedule 30.07.2015