Console.ReadLine()にタイムアウトを追加するにはどうすればよいですか?


122

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

これに取り組む最も簡単な方法は何ですか?

回答:


112

5年経っても、すべての回答が次の1つ以上の問題に悩まされていることを知って驚いています。

  • ReadLine以外の関数が使用されているため、機能が失われています。(前の入力の削除/バックスペース/アップキー)。
  • 関数が複数回呼び出されると、正しく動作しません(複数のスレッドの起動、ReadLineの多くのハング、またはその他の予期しない動作)。
  • 関数はビジー待機に依存しています。待機は数秒からタイムアウトまで(数分になる可能性がある)まで実行されることが予想されるため、これは恐ろしい無駄です。このような大量の時間を実行するビジー待機は、リソースの恐ろしい吸い込みです。これは、マルチスレッドのシナリオでは特に悪いことです。ビジー待機がスリープで変更された場合、これは応答性に悪影響を及ぼしますが、これはおそらく大きな問題ではないことを認めます。

私の解決策は上記の問題に苦しむことなく元の問題を解決すると信じています:

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

もちろん通話はとても簡単です:

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

または、TryXX(out)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;
  }

これは次のように呼ばれます:

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

どちらの場合も、への通話Readerと通常のConsole.ReadLine通話を混在させることはできません。Readerタイムアウトになると、ReadLine通話がハングします。代わりに、通常の(時間指定されていない)ReadLine呼び出しが必要な場合は、を使用しReaderてタイムアウトを省略し、デフォルトで無限のタイムアウトになるようにします。

それで、私が言及した他の解決策のそれらの問題はどうですか?

  • ご覧のとおり、ReadLineを使用して、最初の問題を回避しています。
  • 関数は、複数回呼び出されたときに正しく動作します。タイムアウトが発生するかどうかに関係なく、実行されるバックグラウンドスレッドは1つだけで、アクティブになるのはReadLineへの呼び出しが1つだけです。関数を呼び出すと、常に最新の入力またはタイムアウトが発生し、ユーザーは入力を送信するためにEnterキーを2回以上押す必要がなくなります。
  • そして、明らかに、この関数はビジー待機に依存していません。代わりに、適切なマルチスレッド手法を使用して、リソースの浪費を防ぎます。

この解決策で私が予測する唯一の問題は、スレッドセーフではないことです。ただし、複数のスレッドが実際に同時にユーザーに入力を要求することはできないため、Reader.ReadLineいずれにせよ呼び出しを行う前に同期が行われている必要があります。


1
このコードに続いてNullReferenceExceptionが発生しました。自動イベントが作成されたときにスレッドを開始するのを修正できたと思います。
アウグストペドラザ2014

1
@JSQuareD Sleep(200ms)でのビジー待機はのようなものではないと思いhorrible wasteますが、もちろんあなたのシグナリングは優れています。また、Console.ReadLine2番目の脅威の無限ループで1つのブロッキングコールを使用することで、以下のような非常に賛成の多い他のソリューションのように、バックグラウンドでハングアップするような多くのコールの問題を回避できます。コードを共有していただきありがとうございます。1
ローランド

2
時間内に入力に失敗した場合、このメソッドは最初に行った後続のConsole.ReadLine()呼び出しで中断するようです。あなたは、ReadLine最初に完了する必要がある「ファントム」に終わります。
デレク

1
@Derek残念ながら、このメソッドと通常のReadLine呼び出しを混在させることはできません。すべての呼び出しはReaderを介して行う必要があります。この問題の解決策は、タイムアウトなしでgotInputを待機するリーダーにaethodを追加することです。私は現在モバイルを利用しているので、簡単に回答に追加することはできません。
JSQuareD 2016

1
の必要性はわかりませんgetInput
silvalli

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

2
なぜこれが投票されなかったのかはわかりません。完全に問題なく動作します。他の多くのソリューションには「ReadKey()」が含まれていますが、これは正しく機能しません。つまり、「上」キーを押して以前に入力したコマンドを取得し、バックスペースと矢印キーなど
Contango

9
@Gravitas:これは機能しません。まあ、それは一度動作します。しかし、ReadLineあなたが呼び出すすべてがそこに座って入力を待っています。100回呼び出すと、100個のスレッドが作成され、Enterキーを100回押すまですべてが消えることはありません。
Gabe

2
注意してください。この解決策はきちんと表示されますが、私は何千もの未完了のコールがハングしたままになりました。したがって、繰り返し呼び出される場合は適していません。
トムメイキン

@ Gabe、shakinfree:複数の呼び出しはソリューションでは考慮されず、タイムアウトのある1つの非同期呼び出しのみが考慮されました。ユーザーがコンソールに10個のメッセージを印刷してから、それぞれの入力を1つずつ順番に入力するのはユーザーを混乱させると思います。それでも、ハングしている呼び出しに対して、TimedoutException行にコメントを付けてnull /空の文字列を返すことはできますか?
gp。

いいえ、問題はConsole.ReadLineがまだReadLineDelegateからConsole.ReadLineメソッドを実行しているスレッドプールスレッドをブロックしていることです。
gp。

27

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

これは本当です、OPはブロッキング呼び出しを望んでいるようですが、私は少し考えを震えました...これはおそらくより良い解決策です。
GEOCHET 2008

きっと見たことがあると思います。簡単なgooglesocial.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/…
Gulzar Nazim

ユーザーが何もしないと、この「タイムアウト」がどうなるかわかりません。これにより、キーが押されて他のロジックが継続するまで、ロジックをバックグラウンドで実行し続けることができます。
mphair 2010

確かに、これは修正する必要があります。しかし、ループ条件にタイムアウトを追加するのは簡単です。
ジョナサンアレン

KeyAvailableユーザーがReadLineへの入力を開始したことを示すだけですが、Enterを押すと、ReadLineに戻るイベントが必要になります。このソリューションは、ReadKeyでのみ機能します。つまり、1文字しか取得できません。これはReadLineの実際の質問を解決しないので、私はあなたの解決策を使用できません。-1申し訳ありません
Roland

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

4
これは、すでに組み込まれているツールを使用した最良かつ最も簡単なソリューションだと思います。よくやった!
Vippy

2
綺麗な!シンプルさは本当に究極の洗練です。おめでとうございます!
BrunoSalvino

10

いずれにしても、2番目のスレッドが必要です。非同期IOを使用して、独自のものを宣言しないようにすることができます。

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

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


これは多かれ少なかれ、受け入れられたソリューションが行うことです。
Roland

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

8

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


ええ、キーをポーリングする2番目のスレッドがあり、待機中にアプリが閉じた場合、そのキーポーリングスレッドはそこにとどまって、永久に待機します。
ケリーエルトン

実際には、2番目のスレッド、または「BeginInvoke」を使用したデリゲート(舞台裏でスレッドを使用します。@ gpからの回答を参照してください)。
Contango、2011年

@ kelton52、タスクマネージャでプロセスを終了すると、セカンダリスレッドは終了しますか?
Arlen Beiler

6

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

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

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

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

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

サンプルコード:

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

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

[Enter]キーストロークを現在のプロセス(コンソールアプリ)に送信するための.NET呼び出し?

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


5

デリゲートで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呼び出しは完了しますが、コードはその呼び出しを放棄し、本質的に結果を破棄しました。第 ReadLineメソッド呼び出しがタイムアウトが最終的に期限切れになり、返される値は再びデフォルトになり、ブロックしていきます。

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


1
投稿の「上」という用語は、あいまいで紛らわしいものです。別の回答を参照している場合は、その回答への適切なリンクを作成する必要があります。
bzlm 2008年

5

あなたがいる場合Main()の方法は、使用することはできませんawaitあなたが使用する必要がありますので、Task.WaitAny()

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

ただし、C#7.1では非同期Main()メソッドを作成する可能性があるため、Task.WhenAny()そのオプションがある場合は常にバージョンを使用することをお勧めします。

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

質問を読みすぎているかもしれませんが、待機は、キーを押さない限り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);
}

2

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

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

コンソールが表示されない場合(?)、または入力がファイルから送信される場合は、Console.In.Peek()を使用することもできます。
ジェイミーキットソン

2

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

これを実行すると、うまく機能するように見えました。同僚には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());
      }
   }
}

2
新しいプロセスで別の実行可能ファイルを呼び出して、時間指定されたReadLine()を実行するだけでは、やり過ぎのように聞こえます。代わりに、プロセス全体をセットアップして破棄することで、ReadLine()ブロッキングスレッドを中止できないという問題を本質的に解決しています。
bzlm 2008年

次に、マイクロソフトにこのことを伝えてください。
ジェシーC.スライサー

マイクロソフトはあなたをそのような立場に置かなかった。数行で同じ仕事をする他の答えのいくつかを見てください。上記のコードはある種の賞を受賞するべきだと思います-しかし、あなたが望むようなものではありません:)
Contango

1
いいえ、他のどの回答も、OPが望んでいたことを正確に実行しませんでした。それらのすべては、標準入力ルーチンの機能を失うか、すべての要求Console.ReadLine() ブロックされ、次の要求で入力を保留するという事実にハングアップします。受け入れられた答えはかなり近いですが、それでも制限があります。
ジェシーC.スライサー、2011

1
ええと、そうではありません。入力バッファーは(プログラムがブロックしていなくても)ブロックします。試してみましょう:いくつかの文字を入力しますが、Enterキーを押さないでください。タイムアウトさせてください。呼び出し元で例外をキャプチャします。次にReadLine()、これを呼び出した後、プログラムに別のものを用意します。何が起こるか見てください。のシングルスレッドの性質上、return TWICEを押して実行する必要がありConsoleます。それ。しません。作業。
ジェシーC.スライサー、2011年

2

.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機能を再作成したり、他の危険なハックを実行したりすることはありません。タスクを使用すると、非常に自然な方法で質問を解決できます。


2

ここですでに十分な回答がなかったかのように:0)、以下は上記の静的メソッド@kwlのソリューション(最初のソリューション)にカプセル化します。

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

使用法

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

1

これを解決する簡単なスレッドの例

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

または、行全体を取得するための静的文字列を上に配置します。


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

1

これは素晴らしくて短くありませんか?

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

    // Handle keyInfo value here...
}

1
SpinWaitは一体何ですか?
john ktejik 14

1

これは、グレン・スレイデンのソリューションの完全な例です。別の問題のテストケースを作成するときに、たまたまこれを作成しました。非同期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!");
        }
    }

1

私のコードは完全に友達の答え@JSQuareDに基づいています

しかしStopwatch、タイマーを使用する必要がありました。プログラムを終了すると、Console.ReadKey()それがまだ待っていてConsole.ReadLine()、予期しない動作が発生したためです。

それは私には完璧に働きました。元のConsole.ReadLine()を維持します

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("What is the answer? (5 secs.)");
        try
        {
            var answer = ConsoleReadLine.ReadLine(5000);
            Console.WriteLine("Answer is: {0}", answer);
        }
        catch
        {
            Console.WriteLine("No answer");
        }
        Console.ReadKey();
    }
}

class ConsoleReadLine
{
    private static string inputLast;
    private static Thread inputThread = new Thread(inputThreadAction) { IsBackground = true };
    private static AutoResetEvent inputGet = new AutoResetEvent(false);
    private static AutoResetEvent inputGot = new AutoResetEvent(false);

    static ConsoleReadLine()
    {
        inputThread.Start();
    }

    private static void inputThreadAction()
    {
        while (true)
        {
            inputGet.WaitOne();
            inputLast = Console.ReadLine();
            inputGot.Set();
        }
    }

    // omit the parameter to read a line without a timeout
    public static string ReadLine(int timeout = Timeout.Infinite)
    {
        if (timeout == Timeout.Infinite)
        {
            return Console.ReadLine();
        }
        else
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            while (stopwatch.ElapsedMilliseconds < timeout && !Console.KeyAvailable) ;

            if (Console.KeyAvailable)
            {
                inputGet.Set();
                inputGot.WaitOne();
                return inputLast;
            }
            else
            {
                throw new TimeoutException("User did not provide input within the timelimit.");
            }
        }
    }
}


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

0
string readline = "?";
ThreadPool.QueueUserWorkItem(
    delegate
    {
        readline = Console.ReadLine();
    }
);
do
{
    Thread.Sleep(100);
} while (readline == "?");

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

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

タイムアウトを追加するには、それに合わせてwhileループを変更します。


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

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」文字が入力に挿入されます。ただし、コードを変更することで簡単に除外できます。

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

0

私はこの答えに到達し、最終的に次のことを行いました:

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

を使用した簡単な例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");
}

ユーザーがキーを押して2000ミリ秒以内に移動するとどうなりますか?
Izzy

0

より現代的でタスクベースのコードは次のようになります。

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

私はWindowsアプリケーション(Windowsサービス)を持っているという独特の状況にありました。プログラムを対話的にEnvironment.IsInteractive(VSデバッガーまたは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();
  }    
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.