Frage

Ich habe eine Konsolen-App, die ich dem Benutzer zur Verfügung stellen möchte X Sekunden, um auf die Aufforderung zu antworten.Erfolgt nach einer gewissen Zeit keine Eingabe, soll die Programmlogik weiterlaufen.Wir gehen davon aus, dass ein Timeout eine leere Antwort bedeutet.

Wie kann man das am einfachsten angehen?

War es hilfreich?

Lösung

Ich bin überrascht, dass nach 5 Jahren zu lernen, alle Antworten leiden nach wie vor von einem oder mehreren der folgenden Probleme:

  • Eine andere Funktion als Readline verwendet wird, Funktionalitätsverlust verursacht. (Löschen / Backspace / up-Taste für vorherige Eingabe).
  • Funktion verhält sich schlecht, wenn sie mehrmals aufgerufen (Laich mehrere Threads, viele hängen Readline ist, oder sonst ein unerwartetes Verhalten).
  • Funktion beruht auf einer busy-wait. Das ist eine schreckliche Verschwendung, da die Wartezeit zu erwarten ist überall von einer Anzahl von Sekunden bis zum Timeout laufen, die mehrere Minuten sein könnten. Ein Besetzt warten, die für eine solche ammount Zeit läuft, ist eine schreckliche Ressourcen saugen, die in einem Multi-Threading-Szenario besonders schlecht ist. Wenn das busy-wait mit einem Schlaf modifiziert wird dies wirkt sich negativ auf die Reaktions, obwohl ich zugeben, dass dies wahrscheinlich kein großes Problem.

Ich glaube, meine Lösung wird das ursprüngliche Problem zu lösen, ohne eine der oben genannten Probleme zu leiden:

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

Calling ist, natürlich, sehr einfach:

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

Alternativ können Sie die TryXX(out) Konvention verwenden, wie Shmueli vorgeschlagen:

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

, die aufgerufen wird, wie folgt:

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 beiden Fällen können Sie keine Anrufe mischen mit normalen Reader Anrufe Console.ReadLine: Wenn die Reader Zeiten gibt, wird eine hängende ReadLine Anruf sein. Stattdessen, wenn Sie einen normalen (nicht-timed) ReadLine Anruf haben wollen, nur die Reader verwenden und die Timeout weglassen, so dass es standardmäßig ein Endlos-Timeout.

So wie etwa jene Probleme der anderen Lösungen, die ich erwähnt?

  • Wie Sie sehen können, Readline verwendet wird, das erste Problem zu vermeiden.
  • Die Funktion verhält sich richtig, wenn sie mehrmals aufgerufen. Unabhängig davon, ob ein Timeout auftritt oder nicht, nur ein Hintergrund-Thread jemals läuft und nur höchstens einen Aufruf Readline wird immer aktiv sein. Der Aufruf der Funktion wird in der aktuellen Eingabe immer zur Folge haben, oder in einem Timeout, und der Benutzer wird mehr nicht getroffen eingeben müssen als einmal seine Eingabe einzureichen.
  • Und, natürlich, wird die Funktion nicht auf einer belebten-Warte verlassen. Stattdessen verwendet es die richtigen Multithreading-Techniken Verschwendung von Ressourcen zu vermeiden.

Das einzige Problem, das ich mit dieser Lösung voraussehen ist, dass es nicht Thread-sicher ist. Allerdings können mehrere Threads nicht wirklich den Benutzer fragen, für die Eingabe in der gleichen Zeit, so sollte die Synchronisation, bevor sie einen Anruf geschieht sowieso Reader.ReadLine.

Andere Tipps

string ReadLine(int timeoutms)
{
    ReadLineDelegate d = Console.ReadLine;
    IAsyncResult result = d.BeginInvoke(null, null);
    result.AsyncWaitHandle.WaitOne(timeoutms);//timeout e.g. 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();

Wird dieser Ansatz verwendet Console.KeyAvailable Hilfe ?

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

So oder so Sie einen zweiten Thread tun müssen. Sie könnten asynchrone IO verwenden zu vermeiden, erklären Sie Ihre eigenen:

  • deklariert eine Manual, nennt es "evt"
  • System.Console.OpenStandardInput rufen Sie den Eingangsstrom zu erhalten. Geben Sie eine Callback-Methode, der seine Daten speichert und stellen Sie evt.
  • aufrufen, die Beginread-Methode der Strom einen asynchronen Lesevorgang starten
  • geben Sie dann einen zeitlich Warten auf eine Manual
  • , wenn die Wartezeiten, dann brechen Sie die Lese

Wenn die gelesenen Daten zurückgibt, setzen Sie das Ereignis und Ihr Hauptthread werden weitergehen, sonst werden Sie nach dem Timeout weiter.

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

Das funktioniert für mich.

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

Ich glaube, Sie müssen einen sekundären Thread und Umfrage für eine Taste auf der Konsole machen. Ich weiß nicht von in Art und Weise aufgebaut, dies zu erreichen.

ich mit diesem Problem für 5 Monate zu kämpfen, bevor ich eine Lösung gefunden, die perfekt in einem Unternehmen Einstellung funktioniert.

Das Problem mit den meisten der Lösungen so weit, dass sie anders als Console.ReadLine auf etwas verlassen () und Console.ReadLine () hat viele Vorteile:

  • Unterstützung für Lösch-, Backspace, Pfeiltasten, etc.
  • Die Fähigkeit, die „nach oben“ und wiederholen Sie den letzten Befehl zu drücken (dies ist sehr nützlich, wenn Sie einen Hintergrund-Debugging-Konsole implementieren, die viel Einsatz kommt).

Meine Lösung ist wie folgt:

  1. Spawn einen separaten Thread , um die Benutzereingabe mittels Console.ReadLine zu handhaben ().
  2. Nach der Ablauf der Zeitüberschreitung entblocken Console.ReadLine () durch eine [ENTER] -Taste in die aktuellen Konsolenfenster zu senden, unter Verwendung von http://inputsimulator.codeplex.com/ .

Beispielcode:

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

Weitere Informationen zu diesem Verfahren, einschließlich der richtigen Technik einen Thread abzubrechen, die Console.ReadLine verwendet:

. NET Aufruf [Eingabe] Tastendruck in den aktuellen Prozess zu senden, die eine Konsolenanwendung ist?

Wie einen anderen Thread in .NET, um den Vorgang abzubrechen, wenn der Faden Console.ReadLine ausführt?

Der Aufruf Console.ReadLine () in dem Delegaten ist schlecht, weil, wenn der Benutzer nicht anstößt ‚Enter‘, dann wird dieser Anruf nie wieder zurückkehren. Der Faden den Delegaten Ausführung wird blockiert, bis der Benutzer Hits ‚Enter‘, ohne die Möglichkeit, es zu löschen.

eine Folge dieser Anrufe Ausstellen nicht so verhalten, als man erwarten würde. Betrachten Sie die folgende (am Beispiel Console-Klasse von oben):

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

Der Benutzer kann für die erste Aufforderung der Timeout abläuft, tritt dann in einen Wert für die zweite Aufforderung. Sowohl Vor- und Nachnamen werden die Standardwerte enthalten. Wenn der Benutzer trifft ‚Enter‘, die erste Readline Anruf abgeschlossen wird, aber der Code hat diesen Anruf abandonded und verworfen im Wesentlichen das Ergebnis. Die zweiten Readline Anruf blockieren, wird sich fortsetzen, wird das Timeout schließlich verfallen und der Wert zurückgegeben wird wieder die Standard sein.

BTW Es ist ein Fehler im Code oben. Durch den Aufruf waitHandle.Close () schließen Sie das Ereignis aus unter dem Arbeitsthread. Wenn der Benutzer Treffer ‚Eingabe‘, nachdem die Timeout abgelaufen ist, wird der Arbeiter-Thread versucht, um das Ereignis zu signalisieren, die eine ObjectDisposedException wirft. Die Ausnahme von der Arbeiter-Thread geworfen wird, und wenn Sie nicht Setup haben eine nicht behandelte Exception-Handler Ihr Prozess wird beendet.

Ich kann zu viel in die Frage gestellt werden, zu lesen, aber ich gehe davon aus das Warten auf das Boot-Menü ähnlich sein würde, wo es wartet 15 Sekunden, wenn Sie eine Taste drücken. Sie könnten entweder verwenden (1) eine Sperrfunktion oder (2) Sie einen Thread verwenden könnten, ein Ereignis, und einen Timer. Die Veranstaltung würde als ein ‚Weiter‘ und würde blockieren, bis entweder der Timer abgelaufen ist oder eine Taste gedrückt wurde.

Pseudo-Code für (1) wäre:

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

Wenn Sie in der Main() Methode sind, können Sie nicht await verwenden, so dass Sie Task.WaitAny() verwenden müssen werden:

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

Allerdings, C # 7.1 stellt die Möglichkeit ein asynchronen Main() Verfahren zu schaffen, so ist es besser, die Task.WhenAny() Version zu verwenden, wenn Sie diese Option haben:

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;

kann ich nicht auf Gulzars Beitrag Kommentar leider, aber hier ist ein vollständigeres Beispiel:

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

Bearbeiten : das Problem behoben, indem die eigentliche Arbeit in einem separaten Prozess ausgeführt werden müssen und diesen Prozess, wenn sie zu töten mal aus. Siehe unten für weitere Einzelheiten. Puh!

gab nur diesen einen Lauf und es schien gut zu funktionieren. Mein Kollege hatte eine Version, die ein Thread-Objekt verwendet, aber ich finde die BeginInvoke () -Methode von Delegattypen ein bisschen eleganter sein.

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

Das ReadLine.exe Projekt ist sehr einfach, die eine Klasse hat, die wie so aussieht:

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

.NET 4 macht es unglaublich einfach mit Aufgaben.

Zuerst bauen Sie Ihre Helfer:

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

Zweitens, führen mit einer Aufgabe und warten:

      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

Es gibt keinen Versuch, Readline-Funktionalität oder die Ausführung andere gefährliche Hacks neu erstellen diese Funktion zu erhalten. Aufgaben lassen Sie uns die Frage auf ganz natürliche Art und Weise lösen.

Als ob es hier nicht schon genug Antworten waren: 0), kapselt die folgenden in eine statische Methode @ kwl Lösung oben (die erste).

    public static string ConsoleReadLineWithTimeout(TimeSpan timeout)
    {
        Task<string> task = Task.Factory.StartNew(Console.ReadLine);

        string result = Task.WaitAny(new Task[] { task }, timeout) == 0
            ? task.Result 
            : string.Empty;
        return result;
    }

Verwendung

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

Einfaches Threading Beispiel zur Lösung dieses

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

oder eine statische Zeichenfolge bis oben für eine ganze Zeile zu bekommen.

Im meinem Fall funktionieren:

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

Ist das nicht schön kurz?

if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout))
{
    ConsoleKeyInfo keyInfo = Console.ReadKey();

    // Handle keyInfo value here...
}

Dies ist ein vollständigeres Beispiel für Glen Slayden-Lösung. Ich happended dies zu machen, wenn ein Testfall für ein anderes Problem zu bauen. Es nutzt asynchrone E / A und ein manuelles Reset-Ereignis.

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

Eine weitere günstige Möglichkeit, einen zweiten Thread zu bekommen, ist es in einem Delegierten zu wickeln.

Beispiel Implementierung von Erics Beitrag oben. Dieses spezielle Beispiel wurde verwendet, um Informationen zu lesen, die auf eine Konsole App über die Leitung übergeben wurden:

 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<byte> byteStorage = new List<byte>(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 == "?");

Beachten Sie, dass, wenn Sie den „Console.ReadKey“ Weg gehen, einige der coolen Features von Readline verlieren, nämlich:

  • Unterstützung für Lösch-, Backspace, Pfeiltasten, etc.
  • Die Fähigkeit, die „nach oben“ und wiederholen Sie den letzten Befehl zu drücken (dies ist sehr nützlich, wenn Sie einen Hintergrund-Debugging-Konsole implementieren, die viel Einsatz kommt).

ein Timeout hinzuzufügen, ändern die while-Schleife zu entsprechen.

Bitte nicht hassen mich eine andere Lösung zu der Fülle der vorhandenen Antworten für das Hinzufügen! Dies funktioniert für Console.ReadKey (), aber leicht modifiziert werden könnte, mit Readline () zu arbeiten, etc.

Wie die „Console.Read“ Methoden blockieren, ist es notwendig, die StdIn „ Schub“ streamen die Lese abzubrechen.

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

Hier ist eine Lösung, die Console.KeyAvailable verwendet. Diese werden blockiert Anrufe, aber es sollte ziemlich trivial sein, sie über die TPL asynchron aufgerufen, falls gewünscht. Ich benutzen die üblichen Stornierungsmechanismen, um es einfach mit dem Task asynchronen Muster Draht in und all die guten Sachen.

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

Es gibt einige Nachteile mit diesem.

  • Sie erhalten nicht die Standard-Navigation verfügt, dass ReadLine bietet (Pfeil oben / unten scrollen, usw.).
  • Dies spritzt '\ 0' Zeichen in dem Eingang, wenn eine Sondertaste drücken (F1, PrtScn, etc.). Man könnte sie leicht herausfiltern, indem zwar den Code zu ändern.

hier gelandet, weil eine doppelte Frage gestellt wurde. Ich kam mit der folgenden Lösung auf, die einfach aussieht. Ich bin sicher, dass es einige Nachteile hat mir gefehlt.

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

Ich kam zu dieser Antwort und am Ende tut:

    /// <summary>
    /// Reads Line from console with timeout. 
    /// </summary>
    /// <exception cref="System.TimeoutException">If user does not enter line in the specified time.</exception>
    /// <param name="timeout">Time to wait in milliseconds. Negative value will wait forever.</param>        
    /// <returns></returns>        
    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);                
        }            
    }

Ein einfaches Beispiel mit 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");
}

Viel mehr zeitgemäßen und Task-basierten Code etwa wie folgt aussehen:

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

Ich hatte eine einzigartige Situation, eine Windows-Anwendung (Windows Service) zu haben. Wenn das Programm läuft interaktiv Environment.IsInteractive (VS-Debugger oder von cmd.exe), benutzte ich AttachConsole / AllocConsole meiner stdin / stdout zu bekommen. Um den Prozess zu verhindern, beenden, während die Arbeit getan wurde, ruft der UI-Thread Console.ReadKey(false). Ich wollte das Warten der UI-Thread abzubrechen wurde von einem anderen Thread zu tun, so kam ich mit einer Modifikation der Lösung von @JSquaredD auf.

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

Dies scheint die einfachste, Arbeitslösung zu sein, die keine nativen APIs nicht verwendet:

    static Task<string> ReadLineAsync(CancellationToken cancellation)
    {
        return Task.Run(() =>
        {
            while (!Console.KeyAvailable)
            {
                if (cancellation.IsCancellationRequested)
                    return null;

                Thread.Sleep(100);
            }
            return Console.ReadLine();
        });
    }

Beispiel Nutzung:

    static void Main(string[] args)
    {
        AsyncContext.Run(async () =>
        {
            CancellationTokenSource cancelSource = new CancellationTokenSource();
            cancelSource.CancelAfter(1000);
            Console.WriteLine(await ReadLineAsync(cancelSource.Token) ?? "null");
        });
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top