Domanda

Ho un'applicazione console che voglio dare all'utente x secondi di tempo per rispondere alla richiesta.Se nessun ingresso è effettuata dopo un certo periodo di tempo, il programma di logica dovrebbe continuare.Assumiamo un timeout significa risposta vuota.

Che cosa è il modo più semplice per avvicinarsi a questo?

È stato utile?

Soluzione

Sono sorpreso di apprendere che dopo 5 anni, tutte le risposte ancora soffrono di uno o più dei seguenti problemi:

  • Una funzione ReadLine è utilizzato, con la conseguente perdita di funzionalità.(Backspace/up-chiave per input precedente).
  • La funzione si comporta male quando viene richiamato più volte (deposizione più thread, molti appeso ReadLine, o altrimenti un comportamento imprevisto).
  • La funzione si basa su un busy-wait.Che è un orribile spreco dato che l'attesa è prevista per eseguire ovunque da un certo numero di secondi fino al timeout, che potrebbe essere più minuti.Un occupato-attendere che corre per una tale quantità di tempo è un orribile succhiare risorse, che è particolarmente grave in un multithreading scenario.Se il busy-wait è modificato con un sonno questo ha un effetto negativo sulla capacità di risposta, anche se devo ammettere che forse questo non è un problema enorme.

Credo che la mia soluzione possa risolvere il problema originale, senza affetti da uno qualsiasi dei problemi sopra:

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 è, naturalmente, molto semplice:

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

In alternativa, è possibile utilizzare il TryXX(out) convenzione, come shmueli suggerito:

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

Che si chiama come segue:

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 entrambi i casi, non è possibile mescolare le chiamate a Reader con normale Console.ReadLine chiamate:se il Reader volte, non ci sarà un pensile ReadLine chiamata.Invece, se si desidera avere una normale (non a tempo) ReadLine chiamata, è sufficiente utilizzare il Reader e omettere il timeout, in modo che impostazioni predefinite per un timeout infinito.

Così come su quei problemi di altre soluzioni che ho citato?

  • Come si può vedere, ReadLine è utilizzato, evitando il primo problema.
  • La funzione si comporta correttamente quando viene richiamato più volte.Indipendentemente dal fatto che si verifica un timeout o non, un solo thread in background potrà mai essere in esecuzione e solo al massimo una chiamata a ReadLine sarà sempre attivo.Chiamare la funzione, il risultato sarà sempre aggiornati di immissione o di un timeout, e l'utente non deve colpire inserire più di una volta per presentare il suo ingresso.
  • E, ovviamente, la funzione non si basa su un busy-wait.Invece si utilizza la corretta esecuzione delle tecniche per prevenire lo spreco di risorse.

L'unico problema che prevedo con questa soluzione è che non è thread-safe.Tuttavia, più thread non si può davvero chiedere all'utente per l'input allo stesso tempo, quindi la sincronizzazione dovrebbe accadere prima di effettuare una chiamata a Reader.ReadLine comunque.

Altri suggerimenti

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

Questo approccio utilizza Console.KeyAvailable aiuto?

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

In un modo o nell'altro si ha bisogno di un secondo thread.Si potrebbe utilizzare i / o asincrono per evitare di dichiarare il proprio:

  • dichiarare una ManualResetEvent chiamata "evt"
  • Sistema di chiamata.Console.OpenStandardInput per ottenere il flusso di input.Specificare un metodo di callback che salva i propri dati e impostare evt.
  • chiamata del flusso di BeginRead metodo per avviare un'operazione di lettura asincrona
  • quindi inserire un tempo di attesa di un ManualResetEvent
  • se i tempi di attesa, poi annulla la lettura

Se la lettura restituisce i dati, impostare l'evento e il thread principale si continua, altrimenti si continuerà dopo il timeout.

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

Questo ha funzionato per me.

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

Penso che si avrà bisogno di fare un thread secondario e sondaggio per un tasto sulla console.Io so di non costruito in modo da realizzare.

Ho lottato con questo problema per 5 mesi prima di trovare una soluzione che funziona perfettamente in ambiente enterprise.

Il problema con la maggior parte delle soluzioni finora è che si basano su qualcosa di diverso Console.ReadLine(), e la Console.ReadLine() ha un sacco di vantaggi:

  • Il supporto per eliminare, backspace, i tasti freccia, etc.
  • La capacità di premere il tasto "up" e ripetere l'ultimo comando (questo è molto utile se si implementa un background di debug console che ottiene un sacco di utilizzo).

La mia soluzione è la seguente:

  1. La generazione di un thread separato per gestire l'input dell'utente utilizzando la Console.ReadLine().
  2. Dopo il periodo di timeout, sblocca la Console.ReadLine() inviando un tasto [enter] nella finestra di console corrente, utilizzando http://inputsimulator.codeplex.com/.

Codice di esempio:

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

Ulteriori informazioni su questa tecnica, tra cui la tecnica corretta per interrompere un thread che utilizza la Console.ReadLine:

.NETTO di chiamata per inviare [invio] di battitura nell'attuale processo, che è un'applicazione di console?

Come interrompere un altro thread .NET, quando disse thread è in esecuzione la Console.ReadLine?

La Chiamata Di Console.ReadLine() che il delegato è un male perché, se l'utente non premere 'enter' allora che la chiamata non tornerà mai più.Il thread che esegue il delegato sarà bloccato fino a quando l'utente preme "invio", non c'è alcun modo per annullarla.

L'emissione di una sequenza di queste chiamate non si comportano come ci si aspetterebbe.Si consideri il seguente (utilizzando l'esempio Console di classe dall'alto):

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'utente consente il timeout di scadenza per il primo prompt, quindi immettere un valore per la seconda richiesta.Sia firstName e lastName conterrà i valori di default.Quando l'utente preme "invio", il prima ReadLine chiamata verrà completata, ma il codice non è abandonded la chiamata e essenzialmente scartato il risultato.Il secondo ReadLine telefonata continua a bloccare, il timeout scadrà e il valore restituito sarà di nuovo il default.

BTW - C'è un bug nel codice di cui sopra.Chiamando waitHandle.Close() si chiude l'evento al di fuori del thread di lavoro.Se l'utente preme "invio" dopo la scadenza del timeout, il thread di lavoro tenterà di segnalare l'evento che genera un ObjectDisposedException.L'eccezione viene generata da questo thread, e se non hai l'installazione di un gestore di eccezione non gestita il processo termina.

Mi potrebbe essere la lettura troppo nella questione, ma io parto dal presupposto che l'attesa sarebbe stata simile al menu di avvio dove si attende 15 secondi a meno che non si preme un tasto.Si potrebbe utilizzare (1) una funzione di blocco o (2) si potrebbe utilizzare un filo, un evento, e un timer.L'evento è di agire come un "continua", e consente di bloccare fino a quando il timer è scaduto o è stato premuto un tasto.

Pseudo-codice (1) sarebbe:

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

Se siete in Main() metodo, non è possibile utilizzare await, quindi dovrete utilizzare Task.WaitAny():

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

Tuttavia, C# 7.1 introduce la possibilità di creare un async Main() il metodo, quindi è meglio usare il Task.WhenAny() versione ogni volta che si dispone di tale opzione:

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;

Non posso commentare Gulzar post, purtroppo, ma qui è un fuller esempio:

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

MODIFICA:risolto il problema avendo il lavoro effettivo essere fatto in un processo separato e uccidere il processo se va in time out.Vedi sotto per i dettagli.Wow!

Appena dato questo una corsa che sembrava funzionare bene.Il mio collega aveva una versione di un oggetto del Thread, ma non ho trovato BeginInvoke() metodo di delegare tipi di essere un po ' più elegante.

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

Il ReadLine.exe il progetto è molto semplice, una che ha una classe che sembra così:

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

.Rete 4 fa questo incredibilmente semplice utilizzo di Attività.

Primo, costruire il vostro aiuto:

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

Secondo, l'esecuzione di un compito e di attendere:

      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

C'è il tentativo di ricreare ReadLine funzionalità o l'esecuzione di altri temibili hack per ottenere questo lavoro.Attività cerchiamo di risolvere la questione in un modo molto naturale.

Come se non ci fosse già abbastanza risposte :0), il seguente incapsula in un metodo statico @kwl la soluzione di cui sopra (il primo).

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

Utilizzo

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

Semplice filettatura esempio per risolvere questo

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

o una stringa statica sulla parte superiore per ottenere una linea intera.

Im mio caso funziona bene:

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

Non è questa bella e breve?

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

    // Handle keyInfo value here...
}

Questo è un fuller esempio di Glen Slayden soluzione.Mi è successo di fare questo quando la costruzione di un banco di prova per un altro problema.Utilizza I/O asincrono e un reset manuale dell'evento.

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

Un altro buon modo per ottenere un 2 ° thread è quello di avvolgerlo in un delegato.

Esempio di implementazione di Eric post di cui sopra.Questo particolare esempio è stato utilizzato per leggere le informazioni che è stato passato a una console app tramite tubo:

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

Si noti che se si va giù per la "Console.ReadKey" route, si perde un po ' delle caratteristiche di ReadLine, vale a dire:

  • Il supporto per eliminare, backspace, i tasti freccia, etc.
  • La capacità di premere il tasto "up" e ripetere l'ultimo comando (questo è molto utile se si implementa un background di debug console che ottiene un sacco di utilizzo).

Per aggiungere un timeout, alterare il ciclo while per soddisfare.

Vi prego di non odiarmi per aggiungere un'altra soluzione per la pletora di answers!Questo funziona per la Console.ReadKey(), ma può essere facilmente modificato per funzionare con ReadLine(), etc.

Come la "Console.Leggere" metodi di blocco, è necessario "nudge"StdIn flusso di annullare le leggi.

Sintassi per la chiamata:

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

Codice:

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

Qui è una soluzione che utilizza Console.KeyAvailable.Queste sono chiamate di blocco, ma dovrebbe essere abbastanza banale per chiamare in modo asincrono tramite il TPL, se desiderato.Ho usato la cancellazione standard di meccanismi per rendere più facile il filo con il Compito Modello Asincrono e tutta quella roba buona.

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

Ci sono alcuni svantaggi con questo.

  • Non si ottiene lo standard funzioni di navigazione che ReadLine fornisce (su/giù freccia di scorrimento, etc.).
  • Questo inietta '\0' caratteri in input se una chiave speciale è premere (F1, Stamp, etc.).Si potrebbe facilmente filtrarle, modificando il codice però.

Finito qui, perché un duplicato domanda è stato chiesto.Mi si avvicinò con la seguente soluzione, che sembra semplice.Sono sicuro che ha alcuni inconvenienti che ho perso.

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

Sono giunto a questa risposta e alla fine facendo:

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

Un semplice esempio di utilizzo 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");
}

Molto più attuale e Attività di base codice sarebbe simile a questa:

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

Ho avuto una situazione unica di avere un Applicazione Windows (Windows Service).Quando si esegue il programma in modo interattivo Environment.IsInteractive (VS Debugger o da cmd.exe), ho usato AttachConsole/AllocConsole per ottenere il mio stdin/stdout.Per mantenere il processo, dalla fine mentre il lavoro era stato fatto, il Thread dell'interfaccia utente chiamate Console.ReadKey(false).Volevo annullare l'attesa il thread dell'interfaccia utente stava facendo da un altro thread, così mi si avvicinò con una modifica per la soluzione di @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();
  }    
}

Questa sembra essere la più semplice, soluzione di lavoro, che non utilizza le Api native:

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

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

Esempio di utilizzo:

    static void Main(string[] args)
    {
        AsyncContext.Run(async () =>
        {
            CancellationTokenSource cancelSource = new CancellationTokenSource();
            cancelSource.CancelAfter(1000);
            Console.WriteLine(await ReadLineAsync(cancelSource.Token) ?? "null");
        });
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top