131

プロンプトに応答するためにユーザーにx秒を与えたいコンソール アプリがあります。一定時間入力がない場合、プログラム ロジックは続行する必要があります。タイムアウトは空の応答を意味すると想定しています。

これにアプローチする最も簡単な方法は何ですか?

4

33 に答える 33

33
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();
于 2010-01-11T11:30:36.787 に答える
28

Console.KeyAvailableを使用したこのアプローチは役立ちますか?

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);
    }
}
于 2008-09-11T21:06:45.963 に答える
13

これは私にとってはうまくいきました。

ConsoleKeyInfo k = new ConsoleKeyInfo();
Console.WriteLine("Press any key in the next 5 seconds.");
for (int cnt = 5; cnt > 0; cnt--)
  {
    if (Console.KeyAvailable)
      {
        k = Console.ReadKey();
        break;
      }
    else
     {
       Console.WriteLine(cnt.ToString());
       System.Threading.Thread.Sleep(1000);
     }
 }
Console.WriteLine("The key pressed was " + k.Key);
于 2011-10-05T16:19:49.770 に答える
10

いずれにせよ、2 番目のスレッドが必要です。非同期 IO を使用して、独自の宣言を回避できます。

  • ManualResetEvent を宣言し、「evt」と呼びます
  • System.Console.OpenStandardInput を呼び出して、入力ストリームを取得します。データを保存して evt を設定するコールバック メソッドを指定します。
  • そのストリームの BeginRead メソッドを呼び出して、非同期読み取り操作を開始します
  • 次に、ManualResetEvent で時限待機を入力します
  • 待機がタイムアウトした場合は、読み取りをキャンセルします

読み取りがデータを返す場合は、イベントを設定するとメイン スレッドが続行されます。それ以外の場合は、タイムアウト後に続行されます。

于 2008-09-11T21:18:13.580 に答える
9
// 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.
}
于 2010-08-28T16:41:21.763 に答える
8

セカンダリ スレッドを作成し、コンソールでキーをポーリングする必要があると思います。これを達成するための組み込みの方法はありません。

于 2008-09-11T21:02:09.110 に答える
6

この問題に 5 か月間悩まされた後、エンタープライズ環境で完全に機能するソリューションを見つけました。

これまでのほとんどのソリューションの問題は、Console.ReadLine() 以外のものに依存していることであり、Console.ReadLine() には多くの利点があります。

  • 削除、バックスペース、矢印キーなどのサポート。
  • 「up」キーを押して最後のコマンドを繰り返す機能 (これは、頻繁に使用されるバックグラウンド デバッグ コンソールを実装する場合に非常に便利です)。

私の解決策は次のとおりです。

  1. Console.ReadLine() を使用してユーザー入力を処理する別のスレッドを生成します。
  2. タイムアウト期間が経過したら、 http://inputsimulator.codeplex.com/を使用して現在のコンソール ウィンドウに [enter] キーを送信して Console.ReadLine() のブロックを解除します。

サンプルコード:

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

Console.ReadLine を使用するスレッドを中止する正しい手法を含む、この手法の詳細:

[Enter]キーストロークを現在のプロセスに送信する.NET呼び出し、コンソールアプリはどれですか?

上記のスレッドがConsole.ReadLineを実行しているときに、.NETで別のスレッドを中止する方法は?

于 2012-01-26T10:39:11.013 に答える
6

デリゲートで Console.ReadLine() を呼び出すのは良くありません。ユーザーが「Enter」キーを押さないと、その呼び出しが返されないためです。デリゲートを実行するスレッドは、ユーザーが「Enter」を押すまでブロックされ、キャンセルする方法はありません。

これらの呼び出しのシーケンスを発行すると、期待どおりに動作しません。以下を検討してください (上記の Console クラスの例を使用)。

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

ユーザーは、最初のプロンプトでタイムアウトが切れるのを待ち、2 番目のプロンプトで値を入力します。firstName と lastName の両方にデフォルト値が含まれます。ユーザーが「Enter」を押すと、最初のReadLine 呼び出しが完了しますが、コードはその呼び出しを放棄し、基本的に結果を破棄しています。2 番目のReadLine呼び出しは引き続きブロックされ、タイムアウトは最終的に期限切れになり、返される値は再びデフォルトになります。

ところで-上記のコードにはバグがあります。waitHandle.Close() を呼び出すことで、ワーカー スレッドの下からイベントを閉じます。タイムアウトの期限が切れた後にユーザーが「Enter」を押すと、ワーカー スレッドは ObjectDisposedException をスローするイベントを通知しようとします。例外はワーカー スレッドからスローされ、未処理の例外ハンドラーを設定していない場合、プロセスは終了します。

于 2008-09-11T22:09:41.943 に答える
4

質問を読みすぎているかもしれませんが、キーを押さない限り、15 秒間待機するブート メニューに似た待機時間になると思います。(1) ブロッキング関数を使用するか、(2) スレッド、イベント、およびタイマーを使用することができます。イベントは「継続」として機能し、タイマーが切れるか、キーが押されるまでブロックされます。

(1) の疑似コードは次のようになります。

// 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);
}
于 2008-10-23T20:20:49.527 に答える
3

.NET 4 では、タスクを使用してこれを非常に簡単にします。

まず、ヘルパーをビルドします。

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

次に、タスクを実行して待機します。

      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

これを機能させるために、ReadLine 機能を再作成しようとしたり、他の危険なハックを実行したりする必要はありません。タスクを使用すると、非常に自然な方法で問題を解決できます。

于 2016-01-12T16:37:52.693 に答える
2

残念ながら、Gulzar の投稿にコメントすることはできませんが、より完全な例を次に示します。

            while (Console.KeyAvailable == false)
            {
                Thread.Sleep(250);
                i++;
                if (i > 3)
                    throw new Exception("Timedout waiting for input.");
            }
            input = Console.ReadLine();
于 2010-06-03T15:36:58.550 に答える
2

編集:実際の作業を別のプロセスで実行し、タイムアウトした場合はそのプロセスを強制終了することで問題を修正しました。詳細については、以下を参照してください。うわー!

これを実行しただけで、うまく機能しているように見えました。私の同僚は Thread オブジェクトを使用するバージョンを持っていましたが、デリゲート型の BeginInvoke() メソッドの方がもう少し洗練されていることがわかりました。

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 プロジェクトは、次のような 1 つのクラスを持つ非常に単純なものです。

namespace ReadLine
{
   internal static class Program
   {
      private static void Main()
      {
         System.Console.WriteLine(System.Console.ReadLine());
      }
   }
}
于 2008-09-11T21:30:38.697 に答える
1

私の場合、これはうまくいきます:

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;
}
于 2010-06-04T12:29:21.657 に答える
1

これは、Glen Slayden のソリューションのより完全な例です。別の問題のテストケースを作成するときに、たまたまこれを作成しました。非同期 I/O と手動リセット イベントを使用します。

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!");
        }
    }
于 2013-07-13T07:22:39.090 に答える
0

私は、Windows アプリケーション (Windows サービス) を持っているという独特の状況にありました。プログラムをインタラクティブにEnvironment.IsInteractive(VS Debugger または cmd.exe から) 実行する場合、AttachConsole/AllocConsole を使用して stdin/stdout を取得しました。作業中にプロセスが終了しないようにするために、UI スレッドは を呼び出しますConsole.ReadKey(false)。別のスレッドから UI スレッドが行っていた待機をキャンセルしたかったので、@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();
  }    
}
于 2016-08-25T21:06:07.150 に答える
0
string readline = "?";
ThreadPool.QueueUserWorkItem(
    delegate
    {
        readline = Console.ReadLine();
    }
);
do
{
    Thread.Sleep(100);
} while (readline == "?");

「Console.ReadKey」ルートをたどると、ReadLine の優れた機能の一部が失われることに注意してください。

  • 削除、バックスペース、矢印キーなどのサポート。
  • 「up」キーを押して最後のコマンドを繰り返す機能 (これは、頻繁に使用されるバックグラウンド デバッグ コンソールを実装する場合に非常に便利です)。

タイムアウトを追加するには、while ループを適切に変更します。

于 2011-09-06T11:13:55.620 に答える
0

既存の回答の過多に別の解決策を追加することで私を嫌わないでください! これは Console.ReadKey() で機能しますが、ReadLine() などで機能するように簡単に変更できます。

「Console.Read」メソッドがブロックされているため、StdIn ストリームを「微調整」して読み取りをキャンセルする必要があります。

呼び出し構文:

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

コード:

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);
}
于 2013-07-30T12:02:03.103 に答える
0

を使った解決法をご紹介しますConsole.KeyAvailable。これらは呼び出しをブロックしていますが、必要に応じて TPL を介して非同期に呼び出すのはかなり簡単です。標準のキャンセル メカニズムを使用して、Task Asynchronous Pattern などの優れた機能を簡単に接続できるようにしました。

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

これにはいくつかの欠点があります。

  • ReadLine提供される標準のナビゲーション機能 (上下矢印のスクロールなど)は利用できません。
  • これにより、特殊キー (F1、PrtScn など) が押された場合に、'\0' 文字が入力に挿入されます。ただし、コードを変更することで簡単に除外できます。
于 2013-10-11T14:49:44.730 に答える
0

上記のエリックの投稿の実装例。この特定の例は、パイプ経由でコンソール アプリに渡された情報を読み取るために使用されました。

 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
    }
}
于 2009-05-19T23:26:29.610 に答える
0

2 番目のスレッドを取得するもう 1 つの安価な方法は、デリゲートでラップすることです。

于 2008-09-11T21:20:54.697 に答える
0

重複した質問があったため、ここにたどり着きました。簡単に見える次のソリューションを思いつきました。私が見逃したいくつかの欠点があると確信しています。

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;
    }
}
于 2013-12-17T10:41:09.813 に答える