Comment ajouter un Timeout à la Console.ReadLine()?

j'ai une application console dans laquelle je veux donner à l'utilisateur x secondes pour répondre à l'invite. Si aucune entrée n'est faite après un certain temps, la logique du programme devrait continuer. Nous supposons qu'un délai signifie une réponse vide.

Quelle est la façon la plus directe d'aborder cette question?

113
demandé sur Larsenal 2008-09-12 00:55:57

30 réponses

je suis surpris d'apprendre qu'après 5 ans, toutes les réponses souffrent encore d'un ou plusieurs des problèmes suivants:

  • une fonction autre que ReadLine est utilisée, provoquant une perte de fonctionnalité. (Supprimer/retour arrière/haut-clé pour l'entrée précédente).
  • La fonction
  • se comporte mal lorsqu'elle est invoquée à plusieurs reprises (fraie de multiples threads, beaucoup de ReadLine suspendus, ou un comportement inattendu).
  • fonction s'appuie sur un week-attendre. Ce qui est un horrible gâchis puisque l'attente est prévue pour courir n'importe où à partir d'un certain nombre de secondes jusqu'au temps d'arrêt, qui pourrait être de plusieurs minutes. Un occupé-attendre, s'étendant sur une telle quantité de temps est horrible sucer des ressources, ce qui est particulièrement mauvais dans un multithreading scénario. Si l'attente chargée est modifiée par le sommeil, cela a un effet négatif sur la réactivité, bien que j'admette que ce n'est probablement pas un gros problème.

I croyez que ma solution résoudra le problème original sans souffrir de l'un des problèmes ci-dessus:

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

appeler est, bien sûr, très facile:

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

alternativement, vous pouvez utiliser la convention TryXX(out) , comme shmueli suggéré:

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

qui s'appelle comme suit:

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

dans les deux cas, vous ne pouvez pas mélanger les appels à Reader avec normal Console.ReadLine appels: si le Reader sort, il y aura une pendaison ReadLine appel. Au lieu de cela, si vous voulez avoir un appel normal (non chronométré) ReadLine , il suffit d'utiliser le Reader et d'omettre le timeout, de sorte qu'il est par défaut à un temps infini.

alors qu'en est-il de ces problèmes des autres solutions que j'ai mentionnées?

  • comme vous pouvez le voir, ReadLine est utilisé, en évitant le premier problème.
  • la fonction se comporte correctement lorsqu'il est invoqué plusieurs fois. Indépendamment du fait qu'un timeout se produise ou non, un seul thread de fond sera jamais lancé et au plus un appel à ReadLine sera jamais actif. L'appel de la fonction se traduit toujours par la dernière entrée, ou dans un délai d'expiration, et l'utilisateur n'aura pas à frapper participer plus d'une fois à présenter son entrée.
  • et, évidemment, la fonction ne repose pas sur une attente chargée. Au lieu de cela, il utilise des techniques de multithreading appropriées pour prévenir le gaspillage ressources.

le seul problème que je prévois avec cette solution est qu'il n'est pas thread-safe. Cependant, plusieurs threads ne peuvent pas vraiment demander à l'utilisateur d'entrer en même temps, donc la synchronisation devrait avoir lieu avant de faire un appel à Reader.ReadLine de toute façon.

94
répondu JSQuareD 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
répondu gp. 2012-03-09 08:25:56

cette approche à l'aide de de la Console.Clé disponible aide?

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
répondu Gulzar Nazim 2008-09-11 21:06:45

d'Une manière ou d'une autre vous avez besoin d'un deuxième thread. Vous pouvez utiliser L'IO asynchrone pour éviter de déclarer le vôtre:

  • déclarez un événement manuel, appelez-le" evt "
  • Système d'appel
  • .Console.OpenStandardInput pour obtenir le flux d'entrée. Spécifiez une méthode de callback qui stockera ses données et définira evt.
  • appelez la méthode BeginRead de stream pour démarrer une opération de lecture asynchrone
  • alors entrer un temps d'attente pour un événement manuel
  • si les temps d'attente sont épuisés, annulez la lecture

si la lecture retourne des données, définissez l'événement et votre thread principal continuera, sinon vous continuerez après le timeout.

10
répondu Eric 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
répondu Glenn Slayden 2010-08-28 16:41:21

je pense que vous aurez besoin de faire un thread secondaire et un sondage pour une clé sur la console. Je ne connais aucun moyen d'accomplir cela.

8
répondu GEOCHET 2008-09-11 21:02:09

ça a marché 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.ToString());
       System.Threading.Thread.Sleep(1000);
     }
 }
Console.WriteLine("The key pressed was " + k.Key);
7
répondu user980750 2011-10-05 16:19:49

j'ai lutté avec 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 jusqu'à présent est qu'elles s'appuient sur autre chose que la Console.ReadLine (), et Console.ReadLine() a beaucoup d'avantages:

  • prise en charge des touches delete, backspace, arrow, etc.
  • La possibilité d'appuyer sur le "haut" et de répéter la dernière commande (cet c'est très pratique si vous implémentez une console de débogage d'arrière-plan qui devient très utile).

ma solution est la suivante:

  1. Frayer un thread pour gérer l'entrée de l'utilisateur à l'aide de la Console.ReadLine ().
  2. après la période d'arrêt, débloquer la Console.ReadLine () en envoyant une clé [enter] dans la fenêtre de la console actuelle, en utilisant http://inputsimulator.codeplex.com/ .

code échantillon:

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

plus d'informations sur cette technique, y compris la technique correcte pour interrompre un thread qui utilise la Console.ReadLine:

.NET appel à envoyer [enter] de frappe dans le processus actuel, qui est une application de console?

comment interrompre un autre thread dans .NET, quand ce thread exécute Console.ReadLine?

6
répondu Contango 2017-05-23 12:10:38

J'Appelle La Console.ReadLine () dans le delegate est mauvais parce que si l'utilisateur ne clique pas sur 'enter' alors cet appel ne reviendra jamais. Le thread qui exécute le délégué sera bloqué jusqu'à ce que l'utilisateur atteigne 'entrée', sans possibilité de l'annuler.

l'Émission d'une séquence de ces appels ne se comportera pas comme vous le souhaiteriez. Considérons ce qui suit (en utilisant l'exemple de la classe Console ci-dessus):

System.Console.WriteLine("Enter your first name [John]:");

string firstName = Console.ReadLine(5, "John");

System.Console.WriteLine("Enter your last name [Doe]:");

string lastName = Console.ReadLine(5, "Doe");

l'utilisateur laisse le délai expirer pour le première invite, puis entre une valeur pour la deuxième invite. FirstName et lastName contiennent les valeurs par défaut. Lorsque l'utilisateur accède à 'enter', l'appel ReadLine premier sera terminé, mais le code a abandonné cet appel et a essentiellement écarté le résultat. L'appel second ReadLine continuera à bloquer, le délai expirera et la valeur retournée sera de nouveau la valeur par défaut.

BTW - il y a un bug dans le code ci-dessus. En appelant waitHandle.Fermez () vous fermez l'événement sous le fil worker. Si l'utilisateur accède à' ENTRÉE ' après l'expiration du délai, le thread worker tentera de signaler l'événement qui déclenche une ObjectDisposedException. L'exception est lancée du thread worker, et si vous n'avez pas configuré un handler d'exception non manipulé, votre processus se terminera.

4
répondu Brannon 2008-09-11 22:09:41

je lis peut-être trop dans la question, mais je suppose que l'attente serait similaire au menu de démarrage où il attend 15 secondes à moins que vous appuyez sur une touche. Vous pouvez soit utiliser (1) une fonction de blocage ou (2) vous pouvez utiliser un fil, d'un événement, et une minuterie. L'événement agirait comme un "continue" et bloquerait jusqu'à ce que la minuterie expire ou qu'une touche soit pressée.

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);
}
4
répondu Ryan 2012-08-01 14:21:17

si vous êtes dans la méthode Main() , vous ne pouvez pas utiliser await , donc vous devrez utiliser Task.WaitAny() :

var task = Task.Factory.StartNew(Console.ReadLine);
var result = Task.WaitAny(new Task[] { task }, TimeSpan.FromSeconds(5)) == 0
    ? task.Result : string.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 : string.Empty;
4
répondu kwl 2017-06-27 10:41:52

Je ne peux malheureusement pas commenter le billet 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();
2
répondu Jamie Kitson 2010-06-03 15:36:58

EDIT : a résolu le problème en ayant le travail réel être fait dans un processus séparé et en tuant ce processus si elle times out. Voir ci-dessous pour plus de détails. Ouf!

vient de donner un coup de pouce et ça a l'air de bien marcher. Mon collaborateur avait une version qui utilisait un objet Thread, mais je trouve que la méthode BeginInvoke () des types de délégués est un peu plus élégante.

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

The ReadLine.projet exe est très simple qui a une classe qui ressemble ainsi:

namespace ReadLine
{
   internal static class Program
   {
      private static void Main()
      {
         System.Console.WriteLine(System.Console.ReadLine());
      }
   }
}
2
répondu Jesse C. Slicer 2011-11-17 15:05:15

.NET 4 rend cela incroyablement simple en utilisant des tâches.

d'abord, construisez votre helper:

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

Deuxième, exécuter une tâche et d'attendre:

      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

il n'y a pas d'essayer de recréer la fonctionnalité ReadLine ou d'effectuer d'autres piratages périlleux pour obtenir ce fonctionnement. Tâches résolvons la question d'une manière très naturelle.

2
répondu StevoInco 2016-01-12 16:37:52

exemple de filetage Simple pour résoudre ce

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 corde statique vers le haut pour obtenir une ligne entière.

1
répondu mphair 2010-02-17 16:59:03

Im mon cas, ce beau travail:

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
répondu Sasha 2010-06-04 12:29:21

Ceci est un exemple plus complet de la solution de Glen Slayden. Je suis arrivé à faire cela lors de la construction d'un cas de test pour un autre problème. Il utilise des entrées / sorties 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);
            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
répondu mikemay 2013-07-13 07:22:39

comme s'il n'y avait pas déjà assez de réponses ici :0), le suivant encapsule dans une solution de méthode statique @kwl ci-dessus (la première).

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

Utilisation

    static void Main()
    {
        Console.WriteLine("howdy");
        string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5));
        Console.WriteLine("bye");
    }
1
répondu Nicholas Petersen 2017-09-14 18:49:32

un Autre moyen pas cher pour obtenir un 2ème fil est l'envelopper dans un délégué.

0
répondu Joel Coehoorn 2008-09-11 21:20:54

exemple de mise en œuvre du poste D'Eric ci-dessus. Cet exemple particulier a été utilisé pour lire des informations qui ont été transmises à une console app via pipe:

 using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

namespace PipedInfo
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamReader buffer = ReadPipedInfo();

            Console.WriteLine(buffer.ReadToEnd());
        }

        #region ReadPipedInfo
        public static StreamReader ReadPipedInfo()
        {
            //call with a default value of 5 milliseconds
            return ReadPipedInfo(5);
        }

        public static StreamReader ReadPipedInfo(int waitTimeInMilliseconds)
        {
            //allocate the class we're going to callback to
            ReadPipedInfoCallback callbackClass = new ReadPipedInfoCallback();

            //to indicate read complete or timeout
            AutoResetEvent readCompleteEvent = new AutoResetEvent(false);

            //open the StdIn so that we can read against it asynchronously
            Stream stdIn = Console.OpenStandardInput();

            //allocate a one-byte buffer, we're going to read off the stream one byte at a time
            byte[] singleByteBuffer = new byte[1];

            //allocate a list of an arbitary size to store the read bytes
            List<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
répondu 2009-05-19 23:26:29
string readline = "?";
ThreadPool.QueueUserWorkItem(
    delegate
    {
        readline = Console.ReadLine();
    }
);
do
{
    Thread.Sleep(100);
} while (readline == "?");

notez que si vous descendez la Console".ReadKey "route, vous perdez certains des traits frais de ReadLine, à savoir:

  • prise en charge des touches delete, backspace, arrow, etc.
  • la possibilité d'appuyer sur la touche" up " et de répéter la dernière commande (cela est très pratique si vous implémentez une console de débogage de fond qui est très utile).

pour ajouter un délai, modifier la boucle while à convenir.

0
répondu Contango 2011-09-06 11:40:47

n'est-ce pas joli et court?

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

    // Handle keyInfo value here...
}
0
répondu John Atac 2013-05-12 02:56:55

s'il vous Plaît ne me déteste pas pour ajouter une autre solution à la pléthore de questions / réponses! Cela fonctionne pour la Console.ReadKey(), mais pourrait facilement être modifié pour fonctionner avec ReadLine (), etc.

comme la" Console.Lire "les méthodes bloquent, il est nécessaire de" coup de coude " le flux de 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<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
répondu David Kirkland 2017-05-23 12:10:38

Voici une solution qui utilise Console.KeyAvailable . Ce sont des appels de blocage, mais il devrait être assez trivial de les appeler de façon asynchrone via le TPL si désiré. J'ai utilisé les mécanismes d'annulation standard pour le rendre facile à brancher avec la tâche motif asynchrone et tous ces bons trucs.

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

il y a quelques inconvénients à cela.

  • Vous n'obtenez pas le standard de fonctionnalités de navigation que ReadLine fournit (Flèche Haut/Bas défilant, etc.).
  • cela injecte des caractères '\0' dans l'entrée si une touche spéciale est pressée (F1, PrtScn, etc.). Vous pouvez facilement filtrer les en modifiant le code.
0
répondu Brian Gideon 2013-10-11 14:56:04

a fini ici parce qu'une question en double a été posée. J'ai trouvé la solution suivante qui semble simple. Je suis sûr qu'il y a des inconvénients que j'ai manqués.

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
répondu Frank Rem 2013-12-17 10:41:09

je suis venu à cette réponse et je finis par faire:

    /// <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
répondu Tono Nam 2014-10-03 18:31:24

exemple simple utilisant 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
répondu cprcrack 2015-06-27 13:29:26

code beaucoup plus contemporain et basé sur la tâche ressemblerait à quelque chose comme ceci:

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
répondu Shonn Lyga 2016-06-19 13:26:28

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 de cmd.exe), J'ai utilisé AttachConsole/AllocConsole pour obtenir mon stdin/stdout. Pour empêcher le processus de se terminer pendant que le travail était fait, le fil UI appelle Console.ReadKey(false) . Je voulais annuler l'attente que le thread D'UI faisait à partir d'un autre thread, donc j'ai trouvé 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();
    }
  }

  /// <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
répondu JJS 2016-08-25 21:06:07

cela semble être la solution la plus simple, qui fonctionne, qui n'utilise aucun APIs natif:

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

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

exemple d'usage:

    static void Main(string[] args)
    {
        AsyncContext.Run(async () =>
        {
            CancellationTokenSource cancelSource = new CancellationTokenSource();
            cancelSource.CancelAfter(1000);
            Console.WriteLine(await ReadLineAsync(cancelSource.Token) ?? "null");
        });
    }
0
répondu georgiosd 2017-04-27 07:27:06