Как добавить Timeout в Console.ReadLine ()? - PullRequest
116 голосов
/ 12 сентября 2008

У меня есть консольное приложение, в котором я хочу дать пользователю x секунд, чтобы ответить на приглашение. Если по истечении определенного времени ввод не производится, логика программы должна продолжаться. Мы предполагаем, что тайм-аут означает пустой ответ.

Какой самый простой способ приблизиться к этому?

Ответы [ 32 ]

0 голосов
/ 11 октября 2013

Вот решение, которое использует Console.KeyAvailable. Это блокирующие вызовы, но при желании их асинхронный вызов через TPL должен быть довольно простым. Я использовал стандартные механизмы отмены, чтобы упростить соединение с асинхронным шаблоном задач и всем этим хорошим материалом.

public static class ConsoleEx
{
  public static string ReadLine(TimeSpan timeout)
  {
    var cts = new CancellationTokenSource();
    return ReadLine(timeout, cts.Token);
  }

  public static string ReadLine(TimeSpan timeout, CancellationToken cancellation)
  {
    string line = "";
    DateTime latest = DateTime.UtcNow.Add(timeout);
    do
    {
        cancellation.ThrowIfCancellationRequested();
        if (Console.KeyAvailable)
        {
            ConsoleKeyInfo cki = Console.ReadKey();
            if (cki.Key == ConsoleKey.Enter)
            {
                return line;
            }
            else
            {
                line += cki.KeyChar;
            }
        }
        Thread.Sleep(1);
    }
    while (DateTime.UtcNow < latest);
    return null;
  }
}

Есть некоторые недостатки с этим.

  • Вы не получаете стандартные функции навигации, которые предоставляет ReadLine (прокрутка стрелок вверх / вниз и т. Д.).
  • Это вводит символы \ 0 во ввод, если нажата специальная клавиша (F1, PrtScn и т. Д.). Вы можете легко отфильтровать их, изменив код.
0 голосов
/ 30 июля 2013

Пожалуйста, не ненавидите меня за добавление другого решения к множеству существующих ответов! Это работает для Console.ReadKey (), но может быть легко изменено для работы с ReadLine () и т. Д.

Поскольку методы "Console.Read" блокируются, необходимо " подтолкнуть " поток StdIn для отмены чтения.

Синтаксис вызова:

ConsoleKeyInfo keyInfo;
bool keyPressed = AsyncConsole.ReadKey(500, out keyInfo);
// where 500 is the timeout

Код:

public class AsyncConsole // not thread safe
{
    private static readonly Lazy<AsyncConsole> Instance =
        new Lazy<AsyncConsole>();

    private bool _keyPressed;
    private ConsoleKeyInfo _keyInfo;

    private bool DoReadKey(
        int millisecondsTimeout,
        out ConsoleKeyInfo keyInfo)
    {
        _keyPressed = false;
        _keyInfo = new ConsoleKeyInfo();

        Thread readKeyThread = new Thread(ReadKeyThread);
        readKeyThread.IsBackground = false;
        readKeyThread.Start();

        Thread.Sleep(millisecondsTimeout);

        if (readKeyThread.IsAlive)
        {
            try
            {
                IntPtr stdin = GetStdHandle(StdHandle.StdIn);
                CloseHandle(stdin);
                readKeyThread.Join();
            }
            catch { }
        }

        readKeyThread = null;

        keyInfo = _keyInfo;
        return _keyPressed;
    }

    private void ReadKeyThread()
    {
        try
        {
            _keyInfo = Console.ReadKey();
            _keyPressed = true;
        }
        catch (InvalidOperationException) { }
    }

    public static bool ReadKey(
        int millisecondsTimeout,
        out ConsoleKeyInfo keyInfo)
    {
        return Instance.Value.DoReadKey(millisecondsTimeout, out keyInfo);
    }

    private enum StdHandle { StdIn = -10, StdOut = -11, StdErr = -12 };

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetStdHandle(StdHandle std);

    [DllImport("kernel32.dll")]
    private static extern bool CloseHandle(IntPtr hdl);
}
...