¿Cómo agregar un tiempo de espera a Console.ReadLine ()?

Tengo una aplicación de consola en la que quiero dar al usuario x segundos para responder al aviso. Si no se realiza ninguna entrada después de un cierto período de tiempo, la lógica del progtwig debe continuar. Suponemos que un tiempo de espera significa respuesta vacía.

¿Cuál es la forma más directa de abordar esto?

Estoy sorprendido de saber que después de 5 años, todas las respuestas aún sufren de uno o más de los siguientes problemas:

  • Se usa una función que no sea ReadLine, lo que causa pérdida de funcionalidad. (Borrar / retroceso / tecla arriba para entrada anterior).
  • La función se comporta mal cuando se invoca varias veces (generando múltiples hilos, muchos colgando ReadLine, o comportamiento inesperado).
  • La función se basa en una espera ocupada. Lo cual es un desperdicio horrible, ya que se espera que la espera se ejecute en cualquier lugar desde un número de segundos hasta el tiempo de espera, que puede ser de varios minutos. Una espera ocupada que se ejecuta durante tanto tiempo es una horrible fuente de recursos, lo que es especialmente malo en un escenario de subprocesos múltiples. Si la espera ocupada se modifica con un sueño, esto tiene un efecto negativo en la capacidad de respuesta, aunque admito que probablemente no sea un gran problema.

Creo que mi solución resolverá el problema original sin sufrir ninguno de los problemas anteriores:

class Reader { private static Thread inputThread; private static AutoResetEvent getInput, gotInput; private static string input; static Reader() { getInput = new AutoResetEvent(false); gotInput = new AutoResetEvent(false); inputThread = new Thread(reader); inputThread.IsBackground = true; inputThread.Start(); } private static void reader() { while (true) { getInput.WaitOne(); input = Console.ReadLine(); gotInput.Set(); } } // omit the parameter to read a line without a timeout public static string ReadLine(int timeOutMillisecs = Timeout.Infinite) { getInput.Set(); bool success = gotInput.WaitOne(timeOutMillisecs); if (success) return input; else throw new TimeoutException("User did not provide input within the timelimit."); } } 

Llamar es, por supuesto, muy fácil:

 try { Console.WriteLine("Please enter your name within the next 5 seconds."); string name = Reader.ReadLine(5000); Console.WriteLine("Hello, {0}!", name); } catch (TimeoutException) { Console.WriteLine("Sorry, you waited too long."); } 

Alternativamente, puede usar la TryXX(out) , como sugirió shmueli:

  public static bool TryReadLine(out string line, int timeOutMillisecs = Timeout.Infinite) { getInput.Set(); bool success = gotInput.WaitOne(timeOutMillisecs); if (success) line = input; else line = null; return success; } 

Que se llama de la siguiente manera:

 Console.WriteLine("Please enter your name within the next 5 seconds."); string name; bool success = Reader.TryReadLine(out name, 5000); if (!success) Console.WriteLine("Sorry, you waited too long."); else Console.WriteLine("Hello, {0}!", name); 

En ambos casos, no puede mezclar llamadas a Reader con llamadas normales de Console.ReadLine : si el Reader agota el tiempo de espera, habrá una llamada suspendida ReadLine . En cambio, si desea tener una llamada de ReadLine normal (no temporizada), simplemente use el Reader y omita el tiempo de espera, de forma que se establezca un tiempo de espera infinito.

Entonces, ¿qué hay de los problemas de las otras soluciones que mencioné?

  • Como puede ver, se usa ReadLine, evitando el primer problema.
  • La función se comporta correctamente cuando se invoca varias veces. Independientemente de si se produce un tiempo de espera o no, solo se ejecutará un hilo de fondo y solo se activará una llamada a ReadLine como máximo. Llamar a la función siempre dará como resultado la última entrada, o en un tiempo de espera, y el usuario no tendrá que presionar ingresar más de una vez para enviar su entrada.
  • Y, obviamente, la función no depende de una espera ocupada. En su lugar, utiliza técnicas adecuadas de subprocesamiento múltiple para evitar el desperdicio de recursos.

El único problema que preveo con esta solución es que no es seguro para subprocesos. Sin embargo, los subprocesos múltiples realmente no pueden solicitar al usuario la entrada al mismo tiempo, por lo que la sincronización debería estar sucediendo antes de realizar una llamada a Reader.ReadLine todos modos.

 string ReadLine(int timeoutms) { ReadLineDelegate d = Console.ReadLine; IAsyncResult result = d.BeginInvoke(null, null); result.AsyncWaitHandle.WaitOne(timeoutms);//timeout eg 15000 for 15 secs if (result.IsCompleted) { string resultstr = d.EndInvoke(result); Console.WriteLine("Read: " + resultstr); return resultstr; } else { Console.WriteLine("Timed out!"); throw new TimedoutException("Timed Out!"); } } delegate string ReadLineDelegate(); 

¿Este enfoque utilizará la ayuda de Console.KeyAvailable ?

 class Sample { public static void Main() { ConsoleKeyInfo cki = new ConsoleKeyInfo(); do { Console.WriteLine("\nPress a key to display; press the 'x' key to quit."); // Your code could perform some useful task in the following loop. However, // for the sake of this example we'll merely pause for a quarter second. while (Console.KeyAvailable == false) Thread.Sleep(250); // Loop until input is entered. cki = Console.ReadKey(true); Console.WriteLine("You pressed the '{0}' key.", cki.Key); } while(cki.Key != ConsoleKey.X); } } 

De una forma u otra, necesitas un segundo hilo. Puede usar IO asincrónico para evitar declarar el suyo:

  • declare un ManualResetEvent, llámalo “evt”
  • llama a System.Console.OpenStandardInput para obtener la stream de entrada. Especifique un método de callback que almacenará sus datos y establecerá evt.
  • llamar al método BeginRead de esa secuencia para iniciar una operación de lectura asíncrona
  • luego ingrese una espera temporizada en un evento ManualResetEvent
  • si la espera se agota, cancele la lectura

Si la lectura devuelve datos, configure el evento y su conversación principal continuará; de lo contrario, continuará después del tiempo de espera.

 // Wait for 'Enter' to be pressed or 5 seconds to elapse using (Stream s = Console.OpenStandardInput()) { ManualResetEvent stop_waiting = new ManualResetEvent(false); s.BeginRead(new Byte[1], 0, 1, ar => stop_waiting.Set(), null); // ...do anything else, or simply... stop_waiting.WaitOne(5000); // If desired, other threads could also set 'stop_waiting' // Disposing the stream cancels the async read operation. It can be // re-opened if needed. } 

Creo que necesitarás hacer un hilo secundario y sondear una clave en la consola. No sé de ninguna manera construida para lograr esto.

Esto funcionó para mí.

 ConsoleKeyInfo k = new ConsoleKeyInfo(); Console.WriteLine("Press any key in the next 5 seconds."); for (int cnt = 5; cnt > 0; cnt--) { if (Console.KeyAvailable == true) { k = Console.ReadKey(); break; } else { Console.WriteLine(cnt.ToString()); System.Threading.Thread.Sleep(1000); } } Console.WriteLine("The key pressed was " + k.Key); 

Luché con este problema durante 5 meses antes de encontrar una solución que funcionara perfectamente en un entorno empresarial.

El problema con la mayoría de las soluciones hasta ahora es que confían en algo que no sea Console.ReadLine () y Console.ReadLine () tiene muchas ventajas:

  • Soporte para borrado, retroceso, teclas de flecha, etc.
  • La capacidad de presionar la tecla “arriba” y repetir el último comando (esto es muy útil si implementa una consola de depuración en segundo plano que tiene un gran uso).

Mi solución es la siguiente:

  1. Genere un hilo separado para manejar la entrada del usuario usando Console.ReadLine ().
  2. Después del período de tiempo de espera, desbloquee Console.ReadLine () enviando una clave [enter] a la ventana de la consola actual, utilizando http://inputsimulator.codeplex.com/ .

Código de muestra:

  InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN); 

Más información sobre esta técnica, incluida la técnica correcta para abortar un hilo que usa Console.ReadLine:

Llamada .NET para enviar una pulsación de tecla [enter] en el proceso actual, que es una aplicación de consola?

Cómo abortar otro hilo en .NET, cuando dicho hilo está ejecutando Console.ReadLine?

Llamar a Console.ReadLine () en el delegado es malo porque si el usuario no presiona ‘enter’, esa llamada nunca volverá. El hilo que ejecuta el delegado se bloqueará hasta que el usuario presione ‘enter’, sin posibilidad de cancelarlo.

Emitir una secuencia de estas llamadas no se comportará como es de esperar. Considere lo siguiente (usando la clase de consola de ejemplo de arriba):

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

El usuario deja que expire el tiempo de espera para el primer aviso, luego ingresa un valor para el segundo aviso. Tanto FirstName como lastName contendrán los valores predeterminados. Cuando el usuario pulsa ‘enter’, la primera llamada de ReadLine se completará, pero el código ha abandonado esa llamada y esencialmente descartó el resultado. La segunda llamada a ReadLine continuará bloqueándose, el tiempo de espera expirará y el valor devuelto volverá a ser el predeterminado.

Por cierto, hay un error en el código anterior. Al llamar a waitHandle.Close () cierra el evento desde debajo del subproceso trabajador. Si el usuario pulsa ‘enter’ después de que expira el tiempo de espera, el subproceso de trabajo intentará señalar el evento que arroja una ObjectDisposedException. La excepción se emite desde el subproceso de trabajo, y si no ha configurado un controlador de excepción no controlada, su proceso finalizará.

Quizás esté leyendo demasiado sobre la cuestión, pero supongo que la espera será similar al menú de inicio, donde esperará 15 segundos a menos que presione una tecla. Puede usar (1) una función de locking o (2) puede usar un hilo, un evento y un temporizador. El evento actuaría como ‘continuar’ y se bloquearía hasta que el temporizador expire o se presione una tecla.

El seudocódigo para (1) sería:

 // Get configurable wait time TimeSpan waitTime = TimeSpan.FromSeconds(15.0); int configWaitTimeSec; if (int.TryParse(ConfigManager.AppSetting["DefaultWaitTime"], out configWaitTimeSec)) waitTime = TimeSpan.FromSeconds(configWaitTimeSec); bool keyPressed = false; DateTime expireTime = DateTime.Now + waitTime; // Timer and key processor ConsoleKeyInfo cki; // EDIT: adding a missing ! below while (!keyPressed && (DateTime.Now < expireTime)) { if (Console.KeyAvailable) { cki = Console.ReadKey(true); // TODO: Process key keyPressed = true; } Thread.Sleep(10); } 

Si está en el método Main() , no puede usar await , por lo que deberá usar Task.WaitAny() :

 var task = Task.Factory.StartNew(Console.ReadLine); var result = Task.WaitAny(new Task[] { task }, TimeSpan.FromSeconds(5)) == 0 ? task.Result : string.Empty; 

Sin embargo, C # 7.1 introduce la posibilidad de crear un método asincrónico Main() , por lo que es mejor usar la versión Task.WhenAny() siempre que tenga esa opción:

 var task = Task.Factory.StartNew(Console.ReadLine); var completedTask = await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(5))); var result = object.ReferenceEquals(task, completedTask) ? task.Result : string.Empty; 

No puedo comentar la publicación de Gulzar desafortunadamente, pero aquí hay un ejemplo más completo:

  while (Console.KeyAvailable == false) { Thread.Sleep(250); i++; if (i > 3) throw new Exception("Timedout waiting for input."); } input = Console.ReadLine(); 

EDITAR : solucionó el problema haciendo que el trabajo real se realice en un proceso separado y eliminando ese proceso si se agota el tiempo. Ver abajo para más detalles. ¡Uf!

Solo di una carrera y pareció funcionar bien. Mi compañero de trabajo tenía una versión que usaba un objeto Thread, pero encuentro que el método BeginInvoke () de tipos de delegates es un poco más elegante.

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

El proyecto ReadLine.exe es muy simple y tiene una clase que se ve así:

 namespace ReadLine { internal static class Program { private static void Main() { System.Console.WriteLine(System.Console.ReadLine()); } } } 

.NET 4 lo hace increíblemente simple al usar tareas.

Primero, construye tu ayudante:

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

Segundo, ejecuta con una tarea y espera:

  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 

No hay ningún bash de recrear la funcionalidad ReadLine o realizar otros hacks peligrosos para que funcione. Las tareas nos permiten resolver la pregunta de una manera muy natural.

Ejemplo de subprocesamiento simple para resolver esto

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

o una cuerda estática arriba para obtener una línea completa.

Soy mi caso, esto funciona bien:

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

Este es un ejemplo más completo de la solución de Glen Slayden. Pasé a hacer esto cuando construyo un caso de prueba para otro problema. Utiliza E / S asincrónicas y un evento de restablecimiento manual.

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

Como si ya no hubiera suficientes respuestas aquí: 0), lo siguiente se encapsula en una solución de método estático @ kwl anterior (la primera).

  public static string ConsoleReadLineWithTimeout(TimeSpan timeout) { Task task = Task.Factory.StartNew(Console.ReadLine); string result = Task.WaitAny(new Task[] { task }, timeout) == 0 ? task.Result : string.Empty; return result; } 

Uso

  static void Main() { Console.WriteLine("howdy"); string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5)); Console.WriteLine("bye"); } 

Otra forma barata de obtener un segundo hilo es envolverlo en un delegado.

Ejemplo de implementación de la publicación de Eric anterior. Este ejemplo particular se usó para leer información que se pasó a una aplicación de la consola a través de una tubería:

  using System; using System.Collections.Generic; using System.IO; using System.Threading; namespace PipedInfo { class Program { static void Main(string[] args) { StreamReader buffer = ReadPipedInfo(); Console.WriteLine(buffer.ReadToEnd()); } #region ReadPipedInfo public static StreamReader ReadPipedInfo() { //call with a default value of 5 milliseconds return ReadPipedInfo(5); } public static StreamReader ReadPipedInfo(int waitTimeInMilliseconds) { //allocate the class we're going to callback to ReadPipedInfoCallback callbackClass = new ReadPipedInfoCallback(); //to indicate read complete or timeout AutoResetEvent readCompleteEvent = new AutoResetEvent(false); //open the StdIn so that we can read against it asynchronously Stream stdIn = Console.OpenStandardInput(); //allocate a one-byte buffer, we're going to read off the stream one byte at a time byte[] singleByteBuffer = new byte[1]; //allocate a list of an arbitary size to store the read bytes List byteStorage = new List(4096); IAsyncResult asyncRead = null; int readLength = 0; //the bytes we have successfully read do { //perform the read and wait until it finishes, unless it's already finished asyncRead = stdIn.BeginRead(singleByteBuffer, 0, singleByteBuffer.Length, new AsyncCallback(callbackClass.ReadCallback), readCompleteEvent); if (!asyncRead.CompletedSynchronously) readCompleteEvent.WaitOne(waitTimeInMilliseconds); //end the async call, one way or another //if our read succeeded we store the byte we read if (asyncRead.IsCompleted) { readLength = stdIn.EndRead(asyncRead); if (readLength > 0) byteStorage.Add(singleByteBuffer[0]); } } while (asyncRead.IsCompleted && readLength > 0); //we keep reading until we fail or read nothing //return results, if we read zero bytes the buffer will return empty return new StreamReader(new MemoryStream(byteStorage.ToArray(), 0, byteStorage.Count)); } private class ReadPipedInfoCallback { public void ReadCallback(IAsyncResult asyncResult) { //pull the user-defined variable and strobe the event, the read finished successfully AutoResetEvent readCompleteEvent = asyncResult.AsyncState as AutoResetEvent; readCompleteEvent.Set(); } } #endregion ReadPipedInfo } } 
 string readline = "?"; ThreadPool.QueueUserWorkItem( delegate { readline = Console.ReadLine(); } ); do { Thread.Sleep(100); } while (readline == "?"); 

Tenga en cuenta que si baja por la ruta “Console.ReadKey”, perderá algunas de las interesantes características de ReadLine, a saber:

  • Soporte para borrado, retroceso, teclas de flecha, etc.
  • La capacidad de presionar la tecla “arriba” y repetir el último comando (esto es muy útil si implementa una consola de depuración en segundo plano que tiene un gran uso).

Para agregar un tiempo de espera, modifique el ciclo while para adaptarlo.

¿No es esto lindo y corto?

 if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout)) { ConsoleKeyInfo keyInfo = Console.ReadKey(); // Handle keyInfo value here... } 

¡Por favor, no me odies por agregar otra solución a la gran cantidad de respuestas existentes! Esto funciona para Console.ReadKey (), pero podría modificarse fácilmente para que funcione con ReadLine (), etc.

Como los métodos “Console.Read” están bloqueando, es necesario ” empujar ” la stream StdIn para cancelar la lectura.

Sintaxis de llamadas:

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

Código:

 public class AsyncConsole // not thread safe { private static readonly Lazy Instance = new Lazy(); 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); } 

Aquí hay una solución que usa Console.KeyAvailable . Estas son llamadas de locking, pero debería ser bastante trivial llamarlas de forma asíncrona a través del TPL, si así lo desea. Usé los mecanismos de cancelación estándar para facilitar el cableado con el Patrón asíncrono de tareas y todas esas cosas buenas.

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

Hay algunas desventajas con esto.

  • No obtiene las funciones de navegación estándar que proporciona ReadLine (desplazamiento de flecha hacia arriba / hacia abajo, etc.).
  • Esto inyecta caracteres '\ 0' en la entrada si se presiona una tecla especial (F1, PrtScn, etc.). Sin embargo, puedes fácilmente filtrarlos modificando el código.

Terminó aquí porque se hizo una pregunta duplicada. Se me ocurrió la siguiente solución que se ve directa. Estoy seguro de que tiene algunos inconvenientes que me perdí.

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

Llegué a esta respuesta y terminé haciendo:

  ///  /// Reads Line from console with timeout. ///  /// If user does not enter line in the specified time. /// Time to wait in milliseconds. Negative value will wait forever. ///  public static string ReadLine(int timeout = -1) { ConsoleKeyInfo cki = new ConsoleKeyInfo(); StringBuilder sb = new StringBuilder(); // if user does not want to spesify a timeout if (timeout < 0) return Console.ReadLine(); int counter = 0; while (true) { while (Console.KeyAvailable == false) { counter++; Thread.Sleep(1); if (counter > timeout) throw new System.TimeoutException("Line was not entered in timeout specified"); } cki = Console.ReadKey(false); if (cki.Key == ConsoleKey.Enter) { Console.WriteLine(); return sb.ToString(); } else sb.Append(cki.KeyChar); } } 

Un ejemplo simple usando 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"); } 

Mucho más contemporáneo y el código basado en tareas se vería así:

 public string ReadLine(int timeOutMillisecs) { var inputBuilder = new StringBuilder(); var task = Task.Factory.StartNew(() => { while (true) { var consoleKey = Console.ReadKey(true); if (consoleKey.Key == ConsoleKey.Enter) { return inputBuilder.ToString(); } inputBuilder.Append(consoleKey.KeyChar); } }); var success = task.Wait(timeOutMillisecs); if (!success) { throw new TimeoutException("User did not provide input within the timelimit."); } return inputBuilder.ToString(); } 

Tuve una situación única de tener una aplicación de Windows (Servicio de Windows). Al ejecutar el progtwig de forma interactiva Environment.IsInteractive (VS Debugger o desde cmd.exe), utilicé AttachConsole / AllocConsole para obtener mi stdin / stdout. Para evitar que el proceso finalice mientras se realizaba el trabajo, el Subproceso UI llama a Console.ReadKey(false) . Quería cancelar la espera que estaba haciendo el subproceso de la interfaz de usuario desde otro subproceso, así que se me ocurrió una modificación a la solución por parte de @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(); } } ///  /// Signals the thread that called WaitOne should be allowed to continue ///  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(); } ///  /// Wait until a key is pressed or  is called by another thread ///  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(); } } 

Aquí hay una solución segura que simula la entrada de la consola para desbloquear el hilo después del tiempo de espera. El proyecto https://github.com/IgoSol/ConsoleReader proporciona una implementación de ejemplo de diálogo de usuario.

 var inputLine = ReadLine(5); public static string ReadLine(uint timeoutSeconds, Func countDownMessage, uint samplingFrequencyMilliseconds) { if (timeoutSeconds == 0) return null; var timeoutMilliseconds = timeoutSeconds * 1000; if (samplingFrequencyMilliseconds > timeoutMilliseconds) throw new ArgumentException("Sampling frequency must not be greater then timeout!", "samplingFrequencyMilliseconds"); CancellationTokenSource cts = new CancellationTokenSource(); Task.Factory .StartNew(() => SpinUserDialog(timeoutMilliseconds, countDownMessage, samplingFrequencyMilliseconds, cts.Token), cts.Token) .ContinueWith(t => { var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; PostMessage(hWnd, 0x100, 0x0D, 9); }, TaskContinuationOptions.NotOnCanceled); var inputLine = Console.ReadLine(); cts.Cancel(); return inputLine; } private static void SpinUserDialog(uint countDownMilliseconds, Func countDownMessage, uint samplingFrequencyMilliseconds, CancellationToken token) { while (countDownMilliseconds > 0) { token.ThrowIfCancellationRequested(); Thread.Sleep((int)samplingFrequencyMilliseconds); countDownMilliseconds -= countDownMilliseconds > samplingFrequencyMilliseconds ? samplingFrequencyMilliseconds : countDownMilliseconds; } } [DllImport("User32.Dll", EntryPoint = "PostMessageA")] private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);