J’ai une application de console dans laquelle je veux donner à l’utilisateur x secondes pour répondre à l’invite. Si aucune entrée n’est effectuée après un certain délai, la logique du programme doit continuer. Nous supposons qu’un timeout signifie une réponse vide.
Quelle est la manière la plus simple d’aborder cette question?
Je suis surpris d’apprendre qu’après 5 ans, toutes les réponses souffrent encore d’un ou plusieurs des problèmes suivants:
Je crois que ma solution résoudra le problème initial sans aucun des problèmes ci-dessus:
class Reader { private static Thread inputThread; private static AutoResetEvent getInput, gotInput; private static ssortingng 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 ssortingng 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."); } }
L’appel est, bien sûr, très facile:
try { Console.WriteLine("Please enter your name within the next 5 seconds."); ssortingng name = Reader.ReadLine(5000); Console.WriteLine("Hello, {0}!", name); } catch (TimeoutException) { Console.WriteLine("Sorry, you waited too long."); }
Alternativement, vous pouvez utiliser la TryXX(out)
, comme suggéré par shmueli:
public static bool TryReadLine(out ssortingng line, int timeOutMillisecs = Timeout.Infinite) { getInput.Set(); bool success = gotInput.WaitOne(timeOutMillisecs); if (success) line = input; else line = null; return success; }
Qui s’appelle comme suit:
Console.WriteLine("Please enter your name within the next 5 seconds."); ssortingng name; bool success = Reader.TryReadLine(out name, 5000); if (!success) Console.WriteLine("Sorry, you waited too long."); else Console.WriteLine("Hello, {0}!", name);
Dans les deux cas, vous ne pouvez pas mélanger les appels à Reader
avec les appels normaux de Console.ReadLine
: si le Reader
ReadLine
, il y aura un appel ReadLine
. Au lieu de cela, si vous souhaitez avoir un appel ReadLine
normal (non chronométré), utilisez simplement le Reader
et omettez le délai d’expiration, de sorte qu’il prenne la valeur par défaut pour un délai d’expiration infini.
Alors, qu’en est-il de ces problèmes des autres solutions que j’ai mentionnées?
Le seul problème que je prévois avec cette solution est qu’elle n’est pas compatible avec les threads. Cependant, plusieurs threads ne peuvent pas vraiment demander à l’utilisateur d’entrer en même temps. La synchronisation doit donc avoir lieu avant de faire un appel à Reader.ReadLine
.
ssortingng 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) { ssortingng resultstr = d.EndInvoke(result); Console.WriteLine("Read: " + resultstr); return resultstr; } else { Console.WriteLine("Timed out!"); throw new TimedoutException("Timed Out!"); } } delegate ssortingng ReadLineDelegate();
Cette approche utilisera-t-elle 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); } }
D’une manière ou d’une autre, vous avez besoin d’un deuxième thread. Vous pouvez utiliser des E / S asynchrones pour éviter de déclarer les vôtres:
Si la lecture renvoie des données, définissez l’événement et votre thread principal continuera, sinon vous continuerez après le délai d’attente.
// 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. }
Je pense que vous devrez créer un thread secondaire et rechercher une clé sur la console. Je ne connais aucun moyen construit pour y parvenir.
Cela a fonctionné pour moi.
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.ToSsortingng()); System.Threading.Thread.Sleep(1000); } } Console.WriteLine("The key pressed was " + k.Key);
J’ai rencontré ce problème pendant 5 mois avant de trouver une solution qui fonctionne parfaitement dans un environnement d’entreprise.
Le problème avec la plupart des solutions est qu’elles reposent sur autre chose que Console.ReadLine (), et Console.ReadLine () présente de nombreux avantages:
Ma solution est la suivante:
Exemple de code:
InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);
Plus d’informations sur cette technique, y compris la technique correcte pour abandonner un thread qui utilise Console.ReadLine:
Appel .NET pour envoyer la frappe [enter] dans le processus en cours, qui est une application console?
Comment abandonner un autre thread dans .NET, lorsque ce thread exécute Console.ReadLine?
L’appel de Console.ReadLine () dans le délégué est incorrect car si l’utilisateur ne clique pas sur “Entrée”, cet appel ne sera jamais renvoyé. Le thread exécutant le délégué sera bloqué jusqu’à ce que l’utilisateur clique sur “Entrée”, sans pouvoir l’annuler.
Emettre une séquence de ces appels ne se comportera pas comme prévu. Considérez ce qui suit (en utilisant l’exemple de la classe Console ci-dessus):
System.Console.WriteLine("Enter your first name [John]:"); ssortingng firstName = Console.ReadLine(5, "John"); System.Console.WriteLine("Enter your last name [Doe]:"); ssortingng lastName = Console.ReadLine(5, "Doe");
L’utilisateur laisse expirer le délai d’attente pour la première invite, puis entre une valeur pour la deuxième invite. FirstName et lastName contiendront les valeurs par défaut. Lorsque l’utilisateur clique sur «enter», le premier appel ReadLine se termine, mais le code a abandonné cet appel et a essentiellement ignoré le résultat. Le deuxième appel ReadLine continuera à bloquer, le délai expirera éventuellement et la valeur renvoyée sera à nouveau la valeur par défaut.
BTW- Il y a un bogue dans le code ci-dessus. En appelant waitHandle.Close (), vous fermez l’événement sous le thread de travail. Si l’utilisateur clique sur «Entrée» après expiration du délai d’expiration, le thread de travail tente de signaler l’événement qui génère une exception ObjectDisposedException. L’exception est levée à partir du thread de travail et si vous n’avez pas configuré de gestionnaire d’exceptions non géré, votre processus se terminera.
Je suis peut-être en train de lire trop dans la question, mais je suppose que l’attente serait similaire au menu de démarrage où il attend 15 secondes, sauf si vous appuyez sur une touche. Vous pouvez soit utiliser (1) une fonction de blocage, soit (2) vous pouvez utiliser un thread, un événement et une timer. L’événement agirait comme un «continuer» et bloquerait jusqu’à ce que le temporisateur expire ou qu’une touche soit pressée.
Le pseudo-code pour (1) serait:
// 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 vous êtes dans la méthode Main()
, vous ne pouvez pas utiliser await
, vous devrez donc utiliser Task.WaitAny()
:
var task = Task.Factory.StartNew(Console.ReadLine); var result = Task.WaitAny(new Task[] { task }, TimeSpan.FromSeconds(5)) == 0 ? task.Result : ssortingng.Empty;
Cependant, C # 7.1 introduit la possibilité de créer une méthode Async Main()
, il est donc préférable d’utiliser la version Task.WhenAny()
chaque fois que vous avez cette option:
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 : ssortingng.Empty;
Je ne peux malheureusement pas commenter la publication de Gulzar, mais voici un exemple plus complet:
while (Console.KeyAvailable == false) { Thread.Sleep(250); i++; if (i > 3) throw new Exception("Timedout waiting for input."); } input = Console.ReadLine();
EDIT : le problème a été résolu en effectuant le travail dans un processus séparé et en tuant ce processus s’il expire. Voir ci-dessous pour plus de détails. Ouf!
Je viens juste de donner une chance et ça semblait bien marcher. Mon collègue avait une version qui utilisait un object Thread, mais je trouve que la méthode BeginInvoke () des types de délégué est un peu plus élégante.
namespace TimedReadLine { public static class Console { private delegate ssortingng ReadLineInvoker(); public static ssortingng ReadLine(int timeout) { return ReadLine(timeout, null); } public static ssortingng ReadLine(int timeout, ssortingng @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); } } } }
Le projet ReadLine.exe est un projet très simple qui comporte une classe qui ressemble à ceci:
namespace ReadLine { internal static class Program { private static void Main() { System.Console.WriteLine(System.Console.ReadLine()); } } }
.NET 4 rend cette tâche incroyablement simple en utilisant les tâches.
Tout d’abord, construisez votre assistant:
Private Function AskUser() As Ssortingng Console.Write("Answer my question: ") Return Console.ReadLine() End Function
Deuxièmement, exécutez avec une tâche et attendez:
Dim askTask As Task(Of Ssortingng) = New TaskFactory().StartNew(Function() AskUser()) askTask.Wait(TimeSpan.FromSeconds(30)) If Not askTask.IsCompleted Then Console.WriteLine("User failed to respond.") Else Console.WriteLine(Ssortingng.Format("You responded, '{0}'.", askTask.Result)) End If
Il ne faut pas essayer de recréer la fonctionnalité ReadLine ou d’effectuer d’autres piratages dangereux pour que cela fonctionne. Les tâches nous permettent de résoudre la question de manière très naturelle.
Exemple de filetage simple pour résoudre ce problème
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(); }
ou une chaîne statique en haut pour obtenir une ligne entière.
Im mon cas, cela fonctionne 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(ssortingng[] 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; }
Ceci est un exemple plus complet de la solution de Glen Slayden. Je me suis débrouillé pour le faire lors de la construction d’un test élémentaire pour un autre problème. Il utilise des E / S asynchrones et un événement de réinitialisation manuelle.
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); ssortingng text = System.Text.Encoding.UTF8.GetSsortingng(buffer , 0, numBytes); System.Console.Out.Write(ssortingng.Format( "Thank you for typing: {0}", text)); } else { System.Console.Out.WriteLine("oy, type something!"); } }
Comme s’il n’y avait pas assez de réponses ici: 0), ce qui suit encapsule dans une méthode statique de la solution de @ kwl ci-dessus (la première).
public static ssortingng ConsoleReadLineWithTimeout(TimeSpan timeout) { Task task = Task.Factory.StartNew(Console.ReadLine); ssortingng result = Task.WaitAny(new Task[] { task }, timeout) == 0 ? task.Result : ssortingng.Empty; return result; }
Usage
static void Main() { Console.WriteLine("howdy"); ssortingng result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5)); Console.WriteLine("bye"); }
Un autre moyen peu coûteux d’obtenir un 2ème thread est de l’envelopper dans un délégué.
Exemple d’implémentation du post d’Eric ci-dessus. Cet exemple particulier a été utilisé pour lire les informations transmises à une application de console via pipe:
using System; using System.Collections.Generic; using System.IO; using System.Threading; namespace PipedInfo { class Program { static void Main(ssortingng[] 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 } }
ssortingng readline = "?"; ThreadPool.QueueUserWorkItem( delegate { readline = Console.ReadLine(); } ); do { Thread.Sleep(100); } while (readline == "?");
Notez que si vous descendez la route “Console.ReadKey”, vous perdrez certaines des fonctionnalités intéressantes de ReadLine, à savoir:
Pour append un délai, modifiez la boucle while en conséquence.
N’est-ce pas gentil et court?
if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout)) { ConsoleKeyInfo keyInfo = Console.ReadKey(); // Handle keyInfo value here... }
S’il vous plaît ne me déteste pas pour append une autre solution à la pléthore de réponses existantes! Cela fonctionne pour Console.ReadKey (), mais pourrait facilement être modifié pour fonctionner avec ReadLine (), etc.
Comme les méthodes “Console.Read” bloquent, il est nécessaire de “déplacer” le stream StdIn pour annuler la lecture.
Syntaxe d’appel:
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 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); }
Voici une solution qui utilise Console.KeyAvailable
. Ce sont des appels bloquants, mais il devrait être assez sortingvial de les appeler de manière asynchrone via le TPL si vous le souhaitez. J’ai utilisé les mécanismes d’annulation standard pour faciliter le câblage avec le motif asynchrone de tâches et tout ce qui est bon.
public static class ConsoleEx { public static ssortingng ReadLine(TimeSpan timeout) { var cts = new CancellationTokenSource(); return ReadLine(timeout, cts.Token); } public static ssortingng ReadLine(TimeSpan timeout, CancellationToken cancellation) { ssortingng 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; } }
Cela présente certains inconvénients.
ReadLine
par ReadLine
(défilement des flèches haut / bas, etc.). Terminé ici parce qu’une question en double a été posée. Je suis venu avec la solution suivante qui semble simple. Je suis sûr que cela a des inconvénients que j’ai manqué.
static void Main(ssortingng[] 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; } }
Je suis venu à cette réponse et finis par faire:
/// /// 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 ssortingng ReadLine(int timeout = -1) { ConsoleKeyInfo cki = new ConsoleKeyInfo(); SsortingngBuilder sb = new SsortingngBuilder(); // 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.ToSsortingng(); } else sb.Append(cki.KeyChar); } }
Un exemple simple d’utilisation de 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"); }
Beaucoup plus contemporain et le code basé sur la tâche ressemblerait à ceci:
public ssortingng ReadLine(int timeOutMillisecs) { var inputBuilder = new SsortingngBuilder(); var task = Task.Factory.StartNew(() => { while (true) { var consoleKey = Console.ReadKey(true); if (consoleKey.Key == ConsoleKey.Enter) { return inputBuilder.ToSsortingng(); } inputBuilder.Append(consoleKey.KeyChar); } }); var success = task.Wait(timeOutMillisecs); if (!success) { throw new TimeoutException("User did not provide input within the timelimit."); } return inputBuilder.ToSsortingng(); }
J’ai eu une situation unique d’avoir une application Windows (service Windows). Lors de l’exécution interactive du programme Environment.IsInteractive
(VS Debugger ou à partir de cmd.exe), j’ai utilisé AttachConsole / AllocConsole pour récupérer mon stdin / stdout. Pour empêcher le processus de se terminer pendant le travail, le thread d’interface utilisateur appelle Console.ReadKey(false)
. Je voulais annuler l’attente que le thread UI attendait d’un autre thread, j’ai donc apporté une modification à la solution par @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(); } }
Voici une solution sûre qui simule l’entrée de la console pour débloquer le thread après expiration du délai. Le projet https://github.com/IgoSol/ConsoleReader fournit un exemple d’implémentation de boîte de dialog utilisateur.
var inputLine = ReadLine(5); public static ssortingng 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);