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

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

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

Ответы [ 32 ]

4 голосов
/ 12 сентября 2008

Вызов Console.ReadLine () в делегате - это плохо, потому что если пользователь не нажмет «enter», этот вызов никогда не вернется. Поток, выполняющий делегат, будет заблокирован до тех пор, пока пользователь не нажмет «enter», и отменить его невозможно.

Выдача последовательности этих вызовов не будет вести себя так, как вы ожидаете. Рассмотрим следующее (используя пример класса Console сверху):

System.Console.WriteLine("Enter your first name [John]:");

string firstName = Console.ReadLine(5, "John");

System.Console.WriteLine("Enter your last name [Doe]:");

string lastName = Console.ReadLine(5, "Doe");

Пользователь позволяет истечь тайм-аут для первого приглашения, а затем вводит значение для второго запроса. И firstName, и lastName будут содержать значения по умолчанию. Когда пользователь нажимает «enter», первый вызов ReadLine будет завершен, но код откажется от этого вызова и по существу отбросит результат. Вызов second ReadLine будет продолжать блокироваться, время ожидания истечет, а возвращаемое значение снова станет значением по умолчанию.

Кстати, в приведенном выше коде есть ошибка. Вызывая waitHandle.Close (), вы закрываете событие из-под рабочего потока. Если пользователь нажимает «enter» после истечения времени ожидания, рабочий поток попытается сообщить о событии, которое вызывает исключение ObjectDisposedException. Исключение выдается из рабочего потока, и если вы не настроили обработчик необработанного исключения, ваш процесс завершится.

2 голосов
/ 12 января 2016

.NET 4 делает это невероятно простым с помощью задач.

Сначала создайте своего помощника:

   Private Function AskUser() As String
      Console.Write("Answer my question: ")
      Return Console.ReadLine()
   End Function

Во-вторых, выполнить с задачей и подождать:

      Dim askTask As Task(Of String) = New TaskFactory().StartNew(Function() AskUser())
      askTask.Wait(TimeSpan.FromSeconds(30))
      If Not askTask.IsCompleted Then
         Console.WriteLine("User failed to respond.")
      Else
         Console.WriteLine(String.Format("You responded, '{0}'.", askTask.Result))
      End If

Нет попыток воссоздать функциональность ReadLine или выполнить другие опасные хаки, чтобы заставить это работать. Задачи позволяют решить вопрос очень естественным образом.

2 голосов
/ 03 июня 2010

К сожалению, я не могу комментировать пост Гульзара, но вот более полный пример:

            while (Console.KeyAvailable == false)
            {
                Thread.Sleep(250);
                i++;
                if (i > 3)
                    throw new Exception("Timedout waiting for input.");
            }
            input = Console.ReadLine();
2 голосов
/ 12 сентября 2008

РЕДАКТИРОВАТЬ : исправлена ​​проблема, когда фактическая работа выполнялась в отдельном процессе и убивала этот процесс, если он истекает Смотрите ниже для деталей. Уф!

Просто пробежался, и это, похоже, работало хорошо. У моего коллеги была версия, в которой использовался объект Thread, но я считаю метод BeginInvoke () типов делегатов более элегантным.

namespace TimedReadLine
{
   public static class Console
   {
      private delegate string ReadLineInvoker();

      public static string ReadLine(int timeout)
      {
         return ReadLine(timeout, null);
      }

      public static string ReadLine(int timeout, string @default)
      {
         using (var process = new System.Diagnostics.Process
         {
            StartInfo =
            {
               FileName = "ReadLine.exe",
               RedirectStandardOutput = true,
               UseShellExecute = false
            }
         })
         {
            process.Start();

            var rli = new ReadLineInvoker(process.StandardOutput.ReadLine);
            var iar = rli.BeginInvoke(null, null);

            if (!iar.AsyncWaitHandle.WaitOne(new System.TimeSpan(0, 0, timeout)))
            {
               process.Kill();
               return @default;
            }

            return rli.EndInvoke(iar);
         }
      }
   }
}

Проект ReadLine.exe - очень простой, с одним классом, который выглядит так:

namespace ReadLine
{
   internal static class Program
   {
      private static void Main()
      {
         System.Console.WriteLine(System.Console.ReadLine());
      }
   }
}
2 голосов
/ 14 сентября 2017

Как будто здесь уже не было достаточно ответов: 0), следующее инкапсулируется в статический метод @ kwl's решение выше (первое).

    public static string ConsoleReadLineWithTimeout(TimeSpan timeout)
    {
        Task<string> task = Task.Factory.StartNew(Console.ReadLine);

        string result = Task.WaitAny(new Task[] { task }, timeout) == 0
            ? task.Result 
            : string.Empty;
        return result;
    }

Использование

    static void Main()
    {
        Console.WriteLine("howdy");
        string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5));
        Console.WriteLine("bye");
    }
1 голос
/ 04 июня 2010

В моем случае это нормально работает:

public static ManualResetEvent evtToWait = new ManualResetEvent(false);

private static void ReadDataFromConsole( object state )
{
    Console.WriteLine("Enter \"x\" to exit or wait for 5 seconds.");

    while (Console.ReadKey().KeyChar != 'x')
    {
        Console.Out.WriteLine("");
        Console.Out.WriteLine("Enter again!");
    }

    evtToWait.Set();
}

static void Main(string[] args)
{
        Thread status = new Thread(ReadDataFromConsole);
        status.Start();

        evtToWait = new ManualResetEvent(false);

        evtToWait.WaitOne(5000); // wait for evtToWait.Set() or timeOut

        status.Abort(); // exit anyway
        return;
}
1 голос
/ 13 июля 2013

Это более полный пример решения Глена Слэйдена. Я случайно сделал это при создании контрольного примера для другой проблемы. Он использует асинхронный ввод-вывод и событие ручного сброса.

public static void Main() {
    bool readInProgress = false;
    System.IAsyncResult result = null;
    var stop_waiting = new System.Threading.ManualResetEvent(false);
    byte[] buffer = new byte[256];
    var s = System.Console.OpenStandardInput();
    while (true) {
        if (!readInProgress) {
            readInProgress = true;
            result = s.BeginRead(buffer, 0, buffer.Length
              , ar => stop_waiting.Set(), null);

        }
        bool signaled = true;
        if (!result.IsCompleted) {
            stop_waiting.Reset();
            signaled = stop_waiting.WaitOne(5000);
        }
        else {
            signaled = true;
        }
        if (signaled) {
            readInProgress = false;
            int numBytes = s.EndRead(result);
            string text = System.Text.Encoding.UTF8.GetString(buffer
              , 0, numBytes);
            System.Console.Out.Write(string.Format(
              "Thank you for typing: {0}", text));
        }
        else {
            System.Console.Out.WriteLine("oy, type something!");
        }
    }
1 голос
/ 17 февраля 2010

Простой пример использования потоков

Thread readKeyThread = new Thread(ReadKeyMethod);
static ConsoleKeyInfo cki = null;

void Main()
{
    readKeyThread.Start();
    bool keyEntered = false;
    for(int ii = 0; ii < 10; ii++)
    {
        Thread.Sleep(1000);
        if(readKeyThread.ThreadState == ThreadState.Stopped)
            keyEntered = true;
    }
    if(keyEntered)
    { //do your stuff for a key entered
    }
}

void ReadKeyMethod()
{
    cki = Console.ReadKey();
}

или статическая строка вверх для получения всей строки.

1 голос
/ 12 мая 2013

Разве это не красиво и коротко?

if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout))
{
    ConsoleKeyInfo keyInfo = Console.ReadKey();

    // Handle keyInfo value here...
}
0 голосов
/ 12 сентября 2008

Еще один дешевый способ получить второй поток - заключить его в делегат.

...