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

后端 未结 30 2557
耶瑟儿~
耶瑟儿~ 2020-11-22 04:57

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

相关标签:
30条回答
  • 2020-11-22 05:41

    This is a fuller example of Glen Slayden's solution. I happended to make this when building a test case for another problem. It uses asynchronous I/O and a manual reset event.

    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!");
            }
        }
    
    0 讨论(0)
  • 2020-11-22 05:42

    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?

    0 讨论(0)
  • 2020-11-22 05:43

    A simple example using Console.KeyAvailable:

    Console.WriteLine("Press any key during the next 2 seconds...");
    Thread.Sleep(2000);
    if (Console.KeyAvailable)
    {
        Console.WriteLine("Key pressed");
    }
    else
    {
        Console.WriteLine("You were too slow");
    }
    
    0 讨论(0)
  • 2020-11-22 05:44

    Im my case this work fine:

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 05:44

    Please don't hate me for adding another solution to the plethora of existing answers! This works for Console.ReadKey(), but could easily be modified to work with ReadLine(), etc.

    As the "Console.Read" methods are blocking, it's necessary to "nudge" the StdIn stream to cancel the read.

    Calling syntax:

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

    Code:

    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);
    }
    
    0 讨论(0)
  • 2020-11-22 05:44

    I had a unique situation of having a Windows Application (Windows Service). When running the program interactively Environment.IsInteractive (VS Debugger or from cmd.exe), I used AttachConsole/AllocConsole to get my stdin/stdout. To keep the process from ending while the work was being done, the UI Thread calls Console.ReadKey(false). I wanted to cancel the waiting the UI thread was doing from another thread, so I came up with a modification to the solution by @JSquaredD.

    using System;
    using System.Diagnostics;
    
    internal class PressAnyKey
    {
      private static Thread inputThread;
      private static AutoResetEvent getInput;
      private static AutoResetEvent gotInput;
      private static CancellationTokenSource cancellationtoken;
    
      static PressAnyKey()
      {
        // Static Constructor called when WaitOne is called (technically Cancel too, but who cares)
        getInput = new AutoResetEvent(false);
        gotInput = new AutoResetEvent(false);
        inputThread = new Thread(ReaderThread);
        inputThread.IsBackground = true;
        inputThread.Name = "PressAnyKey";
        inputThread.Start();
      }
    
      private static void ReaderThread()
      {
        while (true)
        {
          // ReaderThread waits until PressAnyKey is called
          getInput.WaitOne();
          // Get here 
          // Inner loop used when a caller uses PressAnyKey
          while (!Console.KeyAvailable && !cancellationtoken.IsCancellationRequested)
          {
            Thread.Sleep(50);
          }
          // Release the thread that called PressAnyKey
          gotInput.Set();
        }
      }
    
      /// <summary>
      /// Signals the thread that called WaitOne should be allowed to continue
      /// </summary>
      public static void Cancel()
      {
        // Trigger the alternate ending condition to the inner loop in ReaderThread
        if(cancellationtoken== null) throw new InvalidOperationException("Must call WaitOne before Cancelling");
        cancellationtoken.Cancel();
      }
    
      /// <summary>
      /// Wait until a key is pressed or <see cref="Cancel"/> is called by another thread
      /// </summary>
      public static void WaitOne()
      {
        if(cancellationtoken==null || cancellationtoken.IsCancellationRequested) throw new InvalidOperationException("Must cancel a pending wait");
        cancellationtoken = new CancellationTokenSource();
        // Release the reader thread
        getInput.Set();
        // Calling thread will wait here indefiniately 
        // until a key is pressed, or Cancel is called
        gotInput.WaitOne();
      }    
    }
    
    0 讨论(0)
提交回复
热议问题