Jak dodać limit czasu do konsoli.ReadLine()?

Mam aplikację konsolową, w której chcę dać użytkownikowi x sekund na odpowiedź na monit. Jeśli po pewnym czasie nie zostanie wykonane żadne wejście, logika programu powinna być kontynuowana. Zakładamy, że timeout oznacza pustą odpowiedź.

Jaki jest najprostszy sposób podejścia do tego?

Author: Larsenal, 2008-09-12

30 answers

Jestem zaskoczony, gdy dowiaduję się, że po 5 latach wszystkie odpowiedzi nadal cierpią na jeden lub więcej z następujących problemów:]}

  • używana jest funkcja inna niż ReadLine, co powoduje utratę funkcjonalności. (Delete / backspace / up-Klawisz dla poprzedniego wejścia).
  • funkcja zachowuje się źle, gdy jest wywoływana wiele razy (wywołanie wielu wątków, wiele zawieszonych linii ReadLine lub inne nieoczekiwane zachowanie).
  • funkcja opiera się na busy-wait. Co jest straszną marnotrawstwem od oczekuje się, że funkcja wait będzie działać w dowolnym miejscu od kilku sekund do limitu czasu, który może wynosić kilka minut. Zapracowane oczekiwanie, które trwa przez taką ilość czasu, jest strasznym ssaniem zasobów, co jest szczególnie złe w scenariuszu wielowątkowym. Jeśli busy-wait jest modyfikowany za pomocą snu, ma to negatywny wpływ na responsywność, choć przyznaję, że prawdopodobnie nie jest to wielki problem.

Wierzę, że moje rozwiązanie rozwiąże oryginalny problem bez cierpienia z któregokolwiek z powyższe problemy:

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

Wywołanie jest oczywiście bardzo proste:

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

Alternatywnie, możesz użyć TryXX(out) konwencji, jak zasugerował 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;
  }

Który nazywa się następująco:

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

W obu przypadkach nie można mieszać połączeń do Reader z normalnymi połączeniami Console.ReadLine: Jeśli Reader przekroczy czas, będzie zawieszone ReadLine połączenie. Zamiast tego, jeśli chcesz mieć normalne (nie timed) wywołanie ReadLine, po prostu użyj Reader i pomiń timeout, tak aby domyślnie było to nieskończony czas.

Więc co z tymi problemami innych rozwiązań, o których wspomniałem?

  • jak widzisz, ReadLine jest używany, unikając pierwszego problemu.
  • funkcja zachowuje się poprawnie, gdy jest wywoływana wiele razy. Niezależnie od tego, czy wystąpi timeout, czy nie, tylko jeden wątek w tle będzie kiedykolwiek uruchomiony i tylko co najwyżej jedno wywołanie ReadLine będzie kiedykolwiek aktywne. Wywołanie funkcji zawsze spowoduje najnowsze wejście lub przekroczenie limitu czasu, a użytkownik nie trzeba nacisnąć enter więcej niż raz, aby złożyć swój wkład.
  • i, oczywiście, funkcja nie polega na busy-wait. Zamiast tego wykorzystuje odpowiednie techniki wielowątkowe, aby zapobiec marnowaniu zasobów.

Jedynym problemem, jaki przewiduję przy tym rozwiązaniu jest to, że nie jest ono bezpieczne dla wątków. Jednak wiele wątków nie może poprosić Użytkownika o wprowadzenie danych w tym samym czasie, więc synchronizacja powinna się odbywać przed wykonaniem połączenia do Reader.ReadLine.

 94
Author: JSQuareD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-28 14:47:23
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();
 32
Author: gp.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-09 08:25:56

Będzie to podejście przy użyciu konsoli.KeyAvailable pomoc?

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);
    }
}
 26
Author: Gulzar Nazim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-11 21:06:45

Tak czy inaczej potrzebujesz drugiego wątku. Możesz użyć asynchronicznego IO, aby uniknąć deklarowania własnego:

  • declare a ManualResetEvent, call it " evt "
  • system połączeń.Konsola.OpenStandardInput, aby pobrać strumień wejściowy. Określ metodę wywołania zwrotnego, która będzie przechowywać swoje dane i ustawić evt.
  • wywołanie metody BeginRead tego strumienia, aby rozpocząć asynchroniczną operację odczytu
  • następnie wprowadź czas oczekiwania na ManualResetEvent
  • Jeśli czas oczekiwania się kończy, anuluj the read

Jeśli read zwróci dane, Ustaw Zdarzenie i twój główny wątek będzie kontynuowany, w przeciwnym razie będziesz kontynuowany po upływie limitu czasu.

 10
Author: Eric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-11 21:18:13
// 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.
}
 9
Author: Glenn Slayden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-28 16:41:21

Myślę, że trzeba będzie zrobić drugi wątek i ankietę dla klucza na konsoli. Nie wiem, jak to osiągnąć.

 8
Author: GEOCHET,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-11 21:02:09

To mi pomogło.

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);
 7
Author: user980750,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-05 16:19:49

Zmagałem się z tym problemem przez 5 miesięcy, zanim znalazłem rozwiązanie, które działa idealnie w środowisku korporacyjnym.

Problem z większością dotychczasowych rozwiązań polega na tym, że polegają one na czymś innym niż konsola.ReadLine () i Console.ReadLine() ma wiele zalet:

  • Obsługa delete, backspace, klawiszy strzałek itp.
  • możliwość naciśnięcia klawisza " up " i powtórzenia ostatniego polecenia (jest to bardzo przydatne, jeśli zaimplementujesz debugowanie w tle konsoli, która jest bardzo przydatna).

Moje rozwiązanie wygląda następująco:

  1. Spawn a oddzielny wątek do obsługi wprowadzania danych przez Użytkownika za pomocą konsoli.ReadLine ().
  2. po upływie limitu czasu odblokuj konsolę.ReadLine () poprzez wysłanie klawisza [enter] do bieżącego okna konsoli, używając http://inputsimulator.codeplex.com/.

Przykładowy kod:

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

Więcej informacji na temat tej techniki, w tym prawidłowa technika przerwania wątku, który używa konsoli.ReadLine:

Wywołanie. NET, aby wysłać [enter] naciśnięcie klawisza do bieżącego procesu, który jest aplikacją konsolową?

Jak przerwać kolejny wątek w. NET, gdy wspomniany wątek wykonuje konsolę.ReadLine?

 6
Author: Contango,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:38

Wywołanie Konsoli.ReadLine () w delegacie jest złe, ponieważ jeśli użytkownik nie naciśnie 'enter', to to wywołanie nigdy nie powróci. Wątek wykonujący delegata zostanie zablokowany, dopóki użytkownik nie naciśnie "enter", bez możliwości anulowania go.

Nadawanie sekwencji tych wywołań nie będzie zachowywało się tak, jak można by się tego spodziewać. Rozważ następujące (używając przykładowej klasy konsoli z góry):

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

Użytkownik pozwala wygasnąć timeout dla pierwszego monitu, a następnie wprowadza wartość dla drugiego / align = "left" / Zarówno firstName, jak i lastName będą zawierać wartości domyślne. Gdy użytkownik naciśnie "enter", pierwsze wywołanie ReadLine zostanie zakończone, ale kod porzucił to wywołanie i zasadniczo odrzucił wynik. second ReadLine będzie nadal blokować, timeout ostatecznie wygaśnie, a zwrócona wartość będzie ponownie domyślna.

BTW - w powyższym kodzie jest błąd. Dzwoniąc do waitHandle.Close() zamykasz Zdarzenie spod workera nić. Jeśli użytkownik naciśnie "enter" po upływie limitu czasu, wątek roboczy spróbuje zasygnalizować zdarzenie, które wyrzuci ObjectDisposedException. Wyjątek jest wyrzucany z wątku roboczego i jeśli nie skonfigurowałeś nieobsługiwanej obsługi wyjątków, proces zostanie zakończony.

 4
Author: Brannon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-11 22:09:41

Może za dużo czytam w tym pytaniu, ale zakładam, że oczekiwanie będzie podobne do menu startowego, gdzie czeka 15 sekund, chyba że naciśniesz klawisz. Możesz użyć (1) funkcji blokującej lub (2) wątku, zdarzenia i timera. Zdarzenie będzie działać jako "continue" i będzie blokowane do czasu wygaśnięcia timera lub naciśnięcia klawisza.

Pseudo-kod dla (1) to:

// 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);
}
 4
Author: Ryan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-01 14:21:17

Jeśli jesteś w metodzie Main(), nie możesz użyć await, więc musisz użyć Task.WaitAny():

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

Jednakże, C# 7.1 wprowadza możliwość tworzenia metody asynchronicznej Main(), więc lepiej używać wersji Task.WhenAny(), Gdy masz taką opcję:

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;
 4
Author: kwl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-27 10:41:52

Nie mogę skomentować postu Gulzara niestety, ale oto pełniejszy przykład:

            while (Console.KeyAvailable == false)
            {
                Thread.Sleep(250);
                i++;
                if (i > 3)
                    throw new Exception("Timedout waiting for input.");
            }
            input = Console.ReadLine();
 2
Author: Jamie Kitson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-03 15:36:58

EDIT : Naprawiono problem poprzez wykonanie rzeczywistej pracy w osobnym procesie i zabicie tego procesu, jeśli czas się skończy. Szczegóły poniżej. Whew!

Po prostu to uruchomiłem i wydawało się, że działa dobrze. Mój współpracownik miał wersję, która używała obiektu wątku, ale uważam, że metoda BeginInvoke() typów delegatów jest nieco bardziej elegancka.
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);
         }
      }
   }
}

ReadLine.exe projekt jest bardzo prosty, który ma jedną klasę, która wygląda tak:

namespace ReadLine
{
   internal static class Program
   {
      private static void Main()
      {
         System.Console.WriteLine(System.Console.ReadLine());
      }
   }
}
 2
Author: Jesse C. Slicer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-17 15:05:15

. NET 4 sprawia, że jest to niezwykle proste za pomocą zadań.

Najpierw zbuduj swojego pomocnika:

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

Po Drugie, wykonaj zadanie i poczekaj:

      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

Nie ma próby odtworzenia funkcji ReadLine lub wykonywania innych niebezpiecznych hacków, aby to zadziałało. Zadania pozwalają nam rozwiązać pytanie w bardzo naturalny sposób.

 2
Author: StevoInco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-12 16:37:52

Prosty przykład wątku do rozwiązania tego

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

Lub statyczny ciąg do góry, aby uzyskać całą linię.

 1
Author: mphair,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-17 16:59:03

Im my case this work fine:

public static ManualResetEvent evtToWait = new ManualResetEvent(false);

private static void ReadDataFromConsole( object state )
{
    Console.WriteLine("Enter \"x\" to exit or wait for 5 seconds.");

    while (Console.ReadKey().KeyChar != 'x')
    {
        Console.Out.WriteLine("");
        Console.Out.WriteLine("Enter again!");
    }

    evtToWait.Set();
}

static void Main(string[] args)
{
        Thread status = new Thread(ReadDataFromConsole);
        status.Start();

        evtToWait = new ManualResetEvent(false);

        evtToWait.WaitOne(5000); // wait for evtToWait.Set() or timeOut

        status.Abort(); // exit anyway
        return;
}
 1
Author: Sasha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-04 12:29:21

Jest to pełniejszy przykład rozwiązania Glena Slaydena. Zdarzyło mi się zrobić to podczas budowania przypadku testowego dla innego problemu. Wykorzystuje asynchroniczne We/Wy i Zdarzenie ręcznego resetowania.

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!");
        }
    }
 1
Author: mikemay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-13 07:22:39

Jakby nie było wystarczająco dużo odpowiedzi tutaj: 0), następujące hermetyzuje w statycznej metodzie @kwl rozwiązanie powyżej (pierwsza).

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

Użycie

    static void Main()
    {
        Console.WriteLine("howdy");
        string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5));
        Console.WriteLine("bye");
    }
 1
Author: Nicholas Petersen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-14 18:49:32

Innym tanim sposobem na zdobycie drugiego wątku jest zawinięcie go w delegata.

 0
Author: Joel Coehoorn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-11 21:20:54

Przykładowa implementacja postu Erica powyżej. Ten konkretny przykład został użyty do odczytania informacji przekazywanych do aplikacji konsolowej za pomocą potoku:

 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
    }
}
 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-19 23:26:29
string readline = "?";
ThreadPool.QueueUserWorkItem(
    delegate
    {
        readline = Console.ReadLine();
    }
);
do
{
    Thread.Sleep(100);
} while (readline == "?");

Zwróć uwagę, że jeśli przejdziesz w dół "konsoli.ReadKey " tracisz niektóre fajne funkcje ReadLine, a mianowicie:

  • Obsługa delete, backspace, klawiszy strzałek itp.
  • możliwość naciśnięcia klawisza " up " i powtórzenia ostatniego polecenia(jest to bardzo przydatne, jeśli zaimplementujesz konsolę debugowania w tle, która ma duże znaczenie).

Aby dodać limit czasu, zmień pętlę while tak, aby pasowała.

 0
Author: Contango,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-06 11:40:47

Czy to nie jest ładne i krótkie?

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

    // Handle keyInfo value here...
}
 0
Author: John Atac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-12 02:56:55

Proszę, nie nienawidź mnie za dodanie innego rozwiązania do mnóstwa istniejących odpowiedzi! To działa dla konsoli.ReadKey (), ale można je łatwo zmodyfikować do pracy z ReadLine (), itd.

Jako " konsola.Odczyt "metody blokują, konieczne jest" szturchnięcie " strumienia StdIn, aby anulować odczyt.

Wywołanie składni:

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

Kod:

public class AsyncConsole // not thread safe
{
    private static readonly Lazy<AsyncConsole> Instance =
        new Lazy<AsyncConsole>();

    private bool _keyPressed;
    private ConsoleKeyInfo _keyInfo;

    private bool DoReadKey(
        int millisecondsTimeout,
        out ConsoleKeyInfo keyInfo)
    {
        _keyPressed = false;
        _keyInfo = new ConsoleKeyInfo();

        Thread readKeyThread = new Thread(ReadKeyThread);
        readKeyThread.IsBackground = false;
        readKeyThread.Start();

        Thread.Sleep(millisecondsTimeout);

        if (readKeyThread.IsAlive)
        {
            try
            {
                IntPtr stdin = GetStdHandle(StdHandle.StdIn);
                CloseHandle(stdin);
                readKeyThread.Join();
            }
            catch { }
        }

        readKeyThread = null;

        keyInfo = _keyInfo;
        return _keyPressed;
    }

    private void ReadKeyThread()
    {
        try
        {
            _keyInfo = Console.ReadKey();
            _keyPressed = true;
        }
        catch (InvalidOperationException) { }
    }

    public static bool ReadKey(
        int millisecondsTimeout,
        out ConsoleKeyInfo keyInfo)
    {
        return Instance.Value.DoReadKey(millisecondsTimeout, out keyInfo);
    }

    private enum StdHandle { StdIn = -10, StdOut = -11, StdErr = -12 };

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetStdHandle(StdHandle std);

    [DllImport("kernel32.dll")]
    private static extern bool CloseHandle(IntPtr hdl);
}
 0
Author: David Kirkland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:38

Oto rozwiązanie, które wykorzystuje Console.KeyAvailable. Są to połączenia blokujące, ale powinno być dość trywialne wywoływanie ich asynchronicznie za pośrednictwem TPL w razie potrzeby. Użyłem standardowych mechanizmów anulowania, aby ułatwić podłączenie się do asynchronicznego wzorca zadań i innych dobrych rzeczy.

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

Są z tym pewne wady.

  • nie otrzymujesz standardowych funkcji nawigacyjnych, które zapewnia ReadLine (przewijanie strzałkami w górę/w dół itp.).
  • to wstrzykuje '\0' znaki na wejście, jeśli naciśnięty jest specjalny klawisz (F1, PrtScn, itd.). Możesz je łatwo odfiltrować, modyfikując kod.
 0
Author: Brian Gideon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 14:56:04

Skończyło się tutaj, ponieważ zadano duplikat pytania. Wymyśliłem następujące rozwiązanie, które wygląda prosto. Jestem pewien, że ma pewne wady, które przegapiłem.

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;
    }
}
 0
Author: Frank Rem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-17 10:41:09

Doszedłem do tej odpowiedzi i skończyłem robiąc:

    /// <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);                
        }            
    }
 0
Author: Tono Nam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-03 18:31:24

Prosty przykład używając Console.KeyAvailable:

Console.WriteLine("Press any key during the next 2 seconds...");
Thread.Sleep(2000);
if (Console.KeyAvailable)
{
    Console.WriteLine("Key pressed");
}
else
{
    Console.WriteLine("You were too slow");
}
 0
Author: cprcrack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-27 13:29:26

Znacznie bardziej współczesny i oparty na zadaniach kod wyglądałby mniej więcej tak:

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();
}
 0
Author: Shonn Lyga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-19 13:26:28

Miałem wyjątkową sytuację posiadania aplikacji Windows (Usługa Windows). Podczas uruchamiania programu interaktywnie Environment.IsInteractive (VS Debugger lub z cmd.exe), użyłem AttachConsole / AllocConsole, aby uzyskać mój stdin / stdout. Aby zapobiec zakończeniu procesu podczas wykonywania pracy, wątek UI wywołuje Console.ReadKey(false). Chciałem anulować oczekiwanie, które wątek UI robił z innego wątku, więc wymyśliłem modyfikację rozwiązania przez @JSquaredD.

using System;
using System.Diagnostics;

internal class PressAnyKey
{
  private static Thread inputThread;
  private static AutoResetEvent getInput;
  private static AutoResetEvent gotInput;
  private static CancellationTokenSource cancellationtoken;

  static PressAnyKey()
  {
    // Static Constructor called when WaitOne is called (technically Cancel too, but who cares)
    getInput = new AutoResetEvent(false);
    gotInput = new AutoResetEvent(false);
    inputThread = new Thread(ReaderThread);
    inputThread.IsBackground = true;
    inputThread.Name = "PressAnyKey";
    inputThread.Start();
  }

  private static void ReaderThread()
  {
    while (true)
    {
      // ReaderThread waits until PressAnyKey is called
      getInput.WaitOne();
      // Get here 
      // Inner loop used when a caller uses PressAnyKey
      while (!Console.KeyAvailable && !cancellationtoken.IsCancellationRequested)
      {
        Thread.Sleep(50);
      }
      // Release the thread that called PressAnyKey
      gotInput.Set();
    }
  }

  /// <summary>
  /// Signals the thread that called WaitOne should be allowed to continue
  /// </summary>
  public static void Cancel()
  {
    // Trigger the alternate ending condition to the inner loop in ReaderThread
    if(cancellationtoken== null) throw new InvalidOperationException("Must call WaitOne before Cancelling");
    cancellationtoken.Cancel();
  }

  /// <summary>
  /// Wait until a key is pressed or <see cref="Cancel"/> is called by another thread
  /// </summary>
  public static void WaitOne()
  {
    if(cancellationtoken==null || cancellationtoken.IsCancellationRequested) throw new InvalidOperationException("Must cancel a pending wait");
    cancellationtoken = new CancellationTokenSource();
    // Release the reader thread
    getInput.Set();
    // Calling thread will wait here indefiniately 
    // until a key is pressed, or Cancel is called
    gotInput.WaitOne();
  }    
}
 0
Author: JJS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-25 21:06:07

Wydaje się być najprostszym, działającym rozwiązaniem, które nie używa żadnych natywnych API:

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

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

Przykładowe użycie:

    static void Main(string[] args)
    {
        AsyncContext.Run(async () =>
        {
            CancellationTokenSource cancelSource = new CancellationTokenSource();
            cancelSource.CancelAfter(1000);
            Console.WriteLine(await ReadLineAsync(cancelSource.Token) ?? "null");
        });
    }
 0
Author: georgiosd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-27 07:27:06