How to add a Timeout to Console.ReadLine()?

2018-12-31 09:26发布

I have a console app in which I want to give the user x seconds to respond to the prompt. If no input is made after a certain period of time, program logic should continue. We assume a timeout means empty response.

What is the most straightforward way of approaching this?

30条回答
梦该遗忘
2楼-- · 2018-12-31 09:42

EDIT: fixed the problem by having the actual work be done in a separate process and killing that process if it times out. See below for details. Whew!

Just gave this a run and it seemed to work nicely. My coworker had a version which used a Thread object, but I find the BeginInvoke() method of delegate types to be a bit more elegant.

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

The ReadLine.exe project is a very simple one which has one class which looks like so:

namespace ReadLine
{
   internal static class Program
   {
      private static void Main()
      {
         System.Console.WriteLine(System.Console.ReadLine());
      }
   }
}
查看更多
骚的不知所云
3楼-- · 2018-12-31 09:42
string readline = "?";
ThreadPool.QueueUserWorkItem(
    delegate
    {
        readline = Console.ReadLine();
    }
);
do
{
    Thread.Sleep(100);
} while (readline == "?");

Note that if you go down the "Console.ReadKey" route, you lose some of the cool features of ReadLine, namely:

  • Support for delete, backspace, arrow keys, etc.
  • The ability to press the "up" key and repeat the last command (this comes in very handy if you implement a background debugging console that gets a lot of use).

To add a timeout, alter the while loop to suit.

查看更多
公子世无双
4楼-- · 2018-12-31 09:43

Ended up here because a duplicate question was asked. I came up with the following solution which looks straightforward. I am sure it has some drawbacks I missed.

static void Main(string[] args)
{
    Console.WriteLine("Hit q to continue or wait 10 seconds.");

    Task task = Task.Factory.StartNew(() => loop());

    Console.WriteLine("Started waiting");
    task.Wait(10000);
    Console.WriteLine("Stopped waiting");
}

static void loop()
{
    while (true)
    {
        if ('q' == Console.ReadKey().KeyChar) break;
    }
}
查看更多
皆成旧梦
5楼-- · 2018-12-31 09:45

Calling Console.ReadLine() in the delegate is bad because if the user doesn't hit 'enter' then that call will never return. The thread executing the delegate will be blocked until the user hits 'enter', with no way to cancel it.

Issuing a sequence of these calls will not behave as you would expect. Consider the following (using the example Console class from above):

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

The user lets the timeout expire for the first prompt, then enters a value for the second prompt. Both firstName and lastName will contain the default values. When the user hits 'enter', the first ReadLine call will complete, but the code has abandonded that call and essentially discarded the result. The second ReadLine call will continue to block, the timeout will eventually expire and the value returned will again be the default.

BTW- There is a bug in the code above. By calling waitHandle.Close() you close the event out from under the worker thread. If the user hits 'enter' after the timeout expires, the worker thread will attempt to signal the event which throws an ObjectDisposedException. The exception is thrown from the worker thread, and if you haven't setup an unhandled exception handler your process will terminate.

查看更多
何处买醉
6楼-- · 2018-12-31 09:45

Another cheap way to get a 2nd thread is to wrap it in a delegate.

查看更多
低头抚发
7楼-- · 2018-12-31 09:46

I struggled with this problem for 5 months before I found an solution that works perfectly in an enterprise setting.

The problem with most of the solutions so far is that they rely on something other than Console.ReadLine(), and Console.ReadLine() has a lot of advantages:

  • Support for delete, backspace, arrow keys, etc.
  • The ability to press the "up" key and repeat the last command (this comes in very handy if you implement a background debugging console that gets a lot of use).

My solution is as follows:

  1. Spawn a separate thread to handle the user input using Console.ReadLine().
  2. After the timeout period, unblock Console.ReadLine() by sending an [enter] key into the current console window, using http://inputsimulator.codeplex.com/.

Sample code:

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

More information on this technique, including the correct technique to abort a thread that uses Console.ReadLine:

.NET call to send [enter] keystroke into the current process, which is a console app?

How to abort another thread in .NET, when said thread is executing Console.ReadLine?

查看更多
登录 后发表回答