Come aggiungere un timeout a Console.ReadLine ()?

Ho un’app console in cui voglio dare all’utente x secondi per rispondere al prompt. Se non viene effettuato alcun input dopo un certo periodo di tempo, la logica del programma dovrebbe continuare. Supponiamo che un timeout significhi una risposta vuota.

Qual è il modo più diretto per avvicinarsi a questo?

Sono sorpreso di apprendere che dopo 5 anni, tutte le risposte continuano a soffrire di uno o più dei seguenti problemi:

  • Viene utilizzata una funzione diversa da ReadLine, con conseguente perdita di funzionalità. (Elimina / backspace / chiave in alto per l’input precedente).
  • La funzione si comporta male quando viene invocata più volte (generando più thread, molti appesi a ReadLine o in altro modo inaspettato).
  • La funzione si basa su un’attesa triggers. Il che è uno spreco orribile dal momento che l’attesa dovrebbe essere eseguita da un numero di secondi fino al timeout, che potrebbe essere di diversi minuti. Un’attesa indaffarata che si svolge per una tale quantità di tempo è un orribile succhiamento di risorse, il che è particolarmente negativo in uno scenario multithreading. Se l’attesa-occupato viene modificata con un sonno, ciò ha un effetto negativo sulla reattività, anche se ammetto che questo non è probabilmente un grosso problema.

Credo che la mia soluzione risolverà il problema originale senza soffrire di nessuno dei suddetti problemi:

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

Chiamare è, ovviamente, molto facile:

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

In alternativa, puoi usare la TryXX(out) , come suggerito da 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; } 

Che è chiamato come segue:

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

In entrambi i casi, non è ansible combinare le chiamate a Reader con le normali chiamate Console.ReadLine : se il Reader scade, verrà eseguita una chiamata ReadLine sospesa. Invece, se si desidera avere una chiamata ReadLine normale (non temporizzata), basta utilizzare il Reader e omettere il timeout, in modo che il valore predefinito sia un timeout infinito.

Che ne dici di quei problemi delle altre soluzioni che ho menzionato?

  • Come puoi vedere, si usa ReadLine, evitando il primo problema.
  • La funzione si comporta correttamente quando viene richiamata più volte. Indipendentemente dal fatto che si verifichi o meno un timeout, solo un thread in background verrà eseguito e solo al massimo una chiamata a ReadLine sarà sempre triggers. Chiamando la funzione risulterà sempre l’ultimo input, o in un timeout, e l’utente non dovrà premere invio più di una volta per inviare il suo input.
  • E, ovviamente, la funzione non si basa su un’attesa indaffarata. Utilizza invece tecniche di multithreading appropriate per evitare sprechi di risorse.

L’unico problema che prevedo con questa soluzione è che non è thread-safe. Tuttavia, più thread non possono realmente chiedere all’utente l’input allo stesso tempo, quindi dovrebbe avvenire la sincronizzazione prima di effettuare comunque una chiamata a Reader.ReadLine .

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

Questo approccio utilizzerà la guida di 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); } } 

In un modo o nell’altro è necessario un secondo thread. È ansible utilizzare l’IO asincrono per evitare di dichiarare il proprio:

  • dichiarare un ManualResetEvent, chiamarlo “evt”
  • chiama System.Console.OpenStandardInput per ottenere il stream di input. Specifica un metodo di callback che archivierà i suoi dati e imposterà evt.
  • chiama il metodo BeginRead di stream per avviare un’operazione di lettura asincrona
  • quindi immettere un’attesa a tempo su un ManualResetEvent
  • se l’attesa è scaduta, annullare la lettura

Se la lettura restituisce i dati, imposta l’evento e il thread principale continuerà, altrimenti continuerai dopo il timeout.

 // 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. } 

Penso che sarà necessario creare un thread secondario e interrogare una chiave sulla console. Non conosco alcun modo costruito per realizzare questo.

Questo ha funzionato per me.

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

Ho faticato con questo problema per 5 mesi prima di trovare una soluzione che funzioni perfettamente in un ambiente aziendale.

Il problema con la maggior parte delle soluzioni finora è che si basano su qualcosa di diverso da Console.ReadLine () e Console.ReadLine () ha molti vantaggi:

  • Supporto per cancellazione, backspace, tasti freccia, ecc.
  • La possibilità di premere il tasto “su” e ripetere l’ultimo comando (questo è molto utile se si implementa una console di debug in background che diventa molto utile).

La mia soluzione è la seguente:

  1. Crea un thread separato per gestire l’input dell’utente usando Console.ReadLine ().
  2. Dopo il periodo di timeout, sbloccare Console.ReadLine () inviando una chiave [invio] nella finestra della console corrente, utilizzando http://inputsimulator.codeplex.com/ .

Codice d’esempio:

  InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN); 

Ulteriori informazioni su questa tecnica, inclusa la tecnica corretta per interrompere una discussione che utilizza Console.ReadLine:

Chiamata .NET per inviare [invio] tasto nel processo corrente, che è un’app console?

Come interrompere un altro thread in .NET, quando detto thread sta eseguendo Console.ReadLine?

Chiamare Console.ReadLine () nel delegato è errato perché se l’utente non preme “invio”, quella chiamata non tornerà mai più. Il thread che esegue il delegato verrà bloccato fino a quando l’utente non farà clic su “invio”, senza possibilità di annullarlo.

L’emissione di una sequenza di queste chiamate non si comporterà come ci si aspetterebbe. Considera quanto segue (usando la class Console di esempio sopra):

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

L’utente lascia scadere il timeout per il primo prompt, quindi inserisce un valore per il secondo prompt. Sia firstName e lastName conterranno i valori predefiniti. Quando l’utente preme “entra”, la prima chiamata ReadLine verrà completata, ma il codice ha abbandonato quella chiamata e sostanzialmente scartato il risultato. La seconda chiamata ReadLine continuerà a bloccarsi, il timeout alla fine scadrà e il valore restituito sarà di nuovo l’impostazione predefinita.

BTW- C’è un bug nel codice qui sopra. Chiamando waitHandle.Close () si chiude l’evento da sotto il thread di lavoro. Se l’utente fa clic su “invio” dopo la scadenza del timeout, il thread worker tenterà di segnalare l’evento che genera un’eccezione ObjectDisposedException. L’eccezione viene generata dal thread di lavoro e, se non è stato impostato un gestore di eccezioni non gestito, il processo verrà terminato.

Potrei leggere troppo nella domanda, ma suppongo che l’attesa sarebbe simile al menu di avvio in cui attende 15 secondi a meno che non si preme un tasto. Potresti usare (1) una funzione di blocco o (2) potresti usare una discussione, un evento e un timer. L’evento avrebbe funzionato come un “continua” e avrebbe bloccato fino a quando il timer non fosse scaduto o non fosse stato premuto un tasto.

Lo pseudo-codice per (1) sarebbe:

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

Se ti trovi nel metodo Main() , non puoi usare await , quindi dovrai usare Task.WaitAny() :

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

Tuttavia, C # 7.1 introduce la possibilità di creare un metodo Main() asincrono, quindi è meglio usare la versione Task.WhenAny() ogni volta che si ha questa opzione:

 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; 

Sfortunatamente non posso commentare il post di Gulzar, ma ecco un esempio più completo:

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

EDIT : risolto il problema facendo eseguire il lavoro effettivo in un processo separato e uccidendolo se scade. Vedi sotto per i dettagli. Meno male!

Ho appena fatto una corsa e sembrava funzionare bene. Il mio collega aveva una versione che utilizzava un object Thread, ma trovo che il metodo BeginInvoke () dei tipi delegati sia un po ‘più 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); } } } } 

Il progetto ReadLine.exe è molto semplice e ha una class che sembra così:

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

.NET 4 rende incredibilmente semplice l’utilizzo di Google Task.

Per prima cosa, costruisci il tuo aiutante:

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

In secondo luogo, esegui un’attività e attendi:

  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 

Non c’è alcun tentativo di ricreare la funzionalità di ReadLine o eseguire altri hack pericolosi per farlo funzionare. I compiti ci permettono di risolvere la questione in modo molto naturale.

Esempio di threading semplice per risolvere questo problema

 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 stringa statica in alto per ottenere un'intera linea.

Nel mio caso questo funziona bene:

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

Questo è un esempio più completo della soluzione di Glen Slayden. Ho pensato di farlo durante la costruzione di un test per un altro problema. Utilizza l’I / O asincrono e un evento di reset manuale.

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

Come se non ci fossero già abbastanza risposte qui: 0), il seguente racchiude in un metodo statico la soluzione di @kwl sopra (la prima).

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

Un altro modo economico per ottenere un secondo thread è avvolgerlo in un delegato.

Esempio di implementazione del post di Eric sopra. Questo particolare esempio è stato utilizzato per leggere le informazioni passate a un’app console tramite pipe:

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

Tieni presente che se scorri la rotta “Console.ReadKey”, perdi alcune delle fantastiche funzionalità di ReadLine, ovvero:

  • Supporto per cancellazione, backspace, tasti freccia, ecc.
  • La possibilità di premere il tasto “su” e ripetere l’ultimo comando (questo è molto utile se si implementa una console di debug in background che diventa molto utile).

Per aggiungere un timeout, modifica il ciclo while per adattarlo.

Non è bello e breve?

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

Per favore non odiarmi per aver aggiunto un’altra soluzione alla pletora di risposte esistenti! Funziona su Console.ReadKey (), ma potrebbe facilmente essere modificato per funzionare con ReadLine (), ecc.

Poiché i metodi “Console.Read” stanno bloccando, è necessario ” spingere ” lo stream StdIn per annullare la lettura.

Chiamata alla syntax:

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

Codice:

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

Ecco una soluzione che utilizza Console.KeyAvailable . Queste stanno bloccando le chiamate, ma dovrebbe essere abbastanza banale chiamarle in modo asincrono tramite il TPL se lo si desidera. Ho usato i meccanismi di cancellazione standard per rendere più semplice il collegamento con il Task Asynchronous Pattern e tutta quella roba buona.

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

Ci sono alcuni svantaggi con questo.

  • Non si ottengono le funzioni di navigazione standard fornite da ReadLine (scorrimento su / giù, ecc.).
  • Inietta '\ 0' caratteri in input se un tasto speciale è premuto (F1, PrtScn, ecc.). Potresti facilmente filtrarli modificando il codice.

Siamo finiti qui perché è stata posta una domanda doppia. Ho trovato la seguente soluzione che sembra diretta. Sono sicuro che ha alcuni inconvenienti che ho perso.

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

Sono arrivato a questa risposta e alla fine ho fatto:

  ///  /// 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 semplice esempio che utilizza 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"); } 

Molto più contemporaneo e il codice basato su attività sarebbe simile a questo:

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

Ho avuto una situazione unica di avere un’applicazione Windows (servizio di Windows). Durante l’esecuzione intertriggers del programma Environment.IsInteractive (VS Debugger o da cmd.exe), ho utilizzato AttachConsole / AllocConsole per ottenere il mio stdin / stdout. Per impedire che il processo finisca mentre veniva eseguito il lavoro, il thread dell’interfaccia utente chiama Console.ReadKey(false) . Volevo cancellare l’attesa che il thread dell’interfaccia utente stava facendo da un altro thread, quindi ho escogitato una modifica alla soluzione da @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(); } } 

Ecco una soluzione sicura che falsifica l’input della console per sbloccare il thread dopo il timeout. Il progetto https://github.com/IgoSol/ConsoleReader fornisce un’implementazione di esempio per la finestra di dialogo dell’utente.

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