130 votes

Comment ajouter un délai d'attente à 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 effectuée après un certain temps, la logique du programme doit continuer. Nous supposons qu'un délai d'attente signifie une réponse vide.

Quelle est la manière la plus simple d'aborder cette question ?

120voto

JSQuareD Points 1240

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 celle ReadLine est utilisée, causant une perte de fonctionnalité. (Supprimer/retour arrière/haut-clé pour l'entrée précédente).
  • La fonction se comporte mal quand il est invoqué à plusieurs reprises (la ponte de plusieurs threads, beaucoup de pendaison de ReadLine, ou sinon un comportement inattendu).
  • Fonction s'appuie sur un week-attendre. Ce qui est horrible déchets car l'attente est prévu pour fonctionner n'importe où à partir d'un certain nombre de secondes jusqu'à l'expiration, qui peut ê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 les occupé-attendre est modifié avec un sommeil, ce qui a un effet négatif sur la réactivité, même si j'avoue que ce n'est probablement pas un énorme problème.

Je crois que ma solution va résoudre le problème d'origine, sans souffrir d'aucun des problèmes ci-dessus:

class Reader {
  private static Thread inputThread;
  private static AutoResetEvent getInput, gotInput;
  private static string input;

  static Reader() {
    inputThread = new Thread(reader);
    inputThread.IsBackground = true;
    inputThread.Start();
    getInput = new AutoResetEvent(false);
    gotInput = new AutoResetEvent(false);
  }

  private static void reader() {
    while (true) {
      getInput.WaitOne();
      input = Console.ReadLine();
      gotInput.Set();
    }
  }

  public static string ReadLine(int timeOutMillisecs) {
    getInput.Set();
    bool success = gotInput.WaitOne(timeOutMillisecs);
    if (success)
      return input;
    else
      throw new TimeoutException("User did not provide input within the timelimit.");
  }
}

L'appel est, bien sûr, très facile:

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

Alors, comment au sujet de ces problèmes de les autres solutions que j'ai mentionné?

  • Comme vous pouvez le voir, ReadLine est utilisée, évitant ainsi le premier problème.
  • La fonction se comporte correctement lorsqu'il est appelé à de multiples reprises. Indépendamment de savoir si un délai d'attente se produit ou pas, un seul thread d'arrière-plan ne sera jamais en cours d'exécution et au plus un appel à l'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 dépend pas d'un occupé-attendre. Au lieu de cela il utilise la bonne techniques de multithreading pour éviter de gaspiller des ressources.

Le seul problème que j'entrevois avec cette solution est qu'elle n'est pas thread-safe. Cependant, plusieurs threads ne pouvez pas vraiment demander à l'utilisateur pour entrer dans le même temps, de sorte que la synchronisation doit se faire avant de faire un appel à l' Reader.ReadLine de toute façon.

1 votes

J'ai obtenu une NullReferenceException en suivant ce code. Je pense que je pourrais fixer le démarrage du fil une fois que les événements automatiques sont créés.

0 votes

@AugustoPedraza À quelle ligne obtenez-vous une NullReferenceException ?

0 votes

@AugustoPedraza Ah oui, je vois ce que vous voulez dire, le AutoResetEvent doivent être initialisés avant le démarrage du fil. J'ai modifié le code dans le post. Merci !

33voto

gp. Points 3015
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();

2 votes

Je ne sais pas pourquoi cette solution n'a pas été votée - elle fonctionne parfaitement. Beaucoup d'autres solutions impliquent "ReadKey()", qui ne fonctionne pas correctement : cela signifie que vous perdez toute la puissance de ReadLine(), comme appuyer sur la touche "haut" pour obtenir la commande précédemment tapée, utiliser la touche retour arrière et les touches fléchées, etc.

9 votes

@Gravitas : Ça ne marche pas. Enfin, ça marche une fois. Mais chaque ReadLine que vous appelez reste là à attendre une entrée. Si vous l'appelez 100 fois, il crée 100 fils qui ne disparaîtront pas tant que vous n'aurez pas appuyé 100 fois sur la touche Entrée !

2 votes

Méfiez-vous. Cette solution semble propre mais je me suis retrouvé avec des milliers d'appels non terminés et laissés en suspens. Donc ne convient pas si l'on est appelé à plusieurs reprises.

28voto

Gulzar Nazim Points 35342

Est-ce que cette approche utilisant Console.KeyAvailable de l'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);
    }
}

0 votes

C'est vrai, le PO semble vouloir un appel de blocage, bien que je frissonne un peu à cette idée... C'est probablement une meilleure solution.

0 votes

Je suis sûr que vous l'avez vu. Je l'ai trouvé dans une recherche rapide sur Google. social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/

0 votes

Je ne vois pas comment cela peut "expirer" si l'utilisateur ne fait rien. Tout ce que cela ferait, c'est éventuellement de continuer à exécuter la logique en arrière-plan jusqu'à ce qu'une touche soit pressée et que d'autres logiques continuent.

10voto

Eric Points 5994

D'une façon ou d'une autre vous avez besoin d'un deuxième thread. Vous pouvez utiliser asynchrone IO pour éviter de déclarer votre propre:

  • déclarer un ManualResetEvent, de l'appeler "evt"
  • Système d'appel.Console.OpenStandardInput pour obtenir le flux d'entrée. Spécifier une méthode de rappel que pour le stockage de ses données et de définir des evt.
  • appel du flux BeginRead méthode pour démarrer une opération de lecture asynchrone
  • entrez ensuite un temps d'attente sur un ManualResetEvent
  • si le temps d'attente, puis annuler la lecture

Si la lecture renvoie les données, définir l'événement et de votre thread principal va continuer, sinon, vous allez continuer après l'expiration du délai.

0 votes

C'est plus ou moins ce que fait la solution acceptée.

8voto

GEOCHET Points 13787

Je pense que vous devrez créer un fil secondaire et demander une clé sur la console. Je ne connais pas de moyen intégré pour accomplir cela.

0 votes

Oui, si vous avez un deuxième thread qui demande des clés, et que votre application se ferme pendant qu'il attend, le thread qui demande les clés va rester là, à attendre, pour toujours.

0 votes

En fait : soit un second thread, soit un délégué avec "BeginInvoke" (qui utilise un thread, dans les coulisses - voir la réponse de @gp).

0 votes

@kelton52, Le fil secondaire s'arrêtera-t-il si vous mettez fin au processus dans le Gestionnaire des tâches ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X