Console.ReadLine ()에 시간 초과를 추가하는 방법은 무엇입니까?


122

사용자에게 프롬프트에 응답하는 데 x 초를 주고 싶은 콘솔 앱이 있습니다. 일정 시간이 지나도 입력이 없으면 프로그램 로직이 계속됩니다. 타임 아웃은 빈 응답을 의미한다고 가정합니다.

이에 접근하는 가장 직접적인 방법은 무엇입니까?

답변:


112

5 년이 지난 후에도 모든 답변이 여전히 다음 문제 중 하나 이상을 겪고 있다는 사실에 놀랐습니다.

  • 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이 사용되어 첫 번째 문제를 피합니다.
  • 함수는 여러 번 호출 될 때 제대로 작동합니다. 시간 초과 발생 여부에 관계없이 백그라운드 스레드는 하나만 실행되고 ReadLine에 대한 호출은 최대 한 번만 활성화됩니다. 함수를 호출하면 항상 최신 입력 또는 시간 초과가 발생하며 사용자는 입력을 제출하기 위해 Enter 키를 두 번 이상 누를 필요가 없습니다.
  • 그리고 분명히이 기능은 바쁜 대기에 의존하지 않습니다. 대신 적절한 멀티 스레딩 기술을 사용하여 리소스 낭비를 방지합니다.

이 솔루션으로 예상되는 유일한 문제는 스레드로부터 안전하지 않다는 것입니다. 그러나 여러 스레드는 실제로 사용자에게 동시에 입력을 요청할 수 없으므로 Reader.ReadLine어쨌든 호출하기 전에 동기화가 이루어져야 합니다.


1
이 코드 다음에 NullReferenceException이 발생했습니다. 자동 이벤트가 생성되면 스레드 시작을 한 번 수정할 수 있다고 생각합니다.
Augusto Pedraza 2014 년

1
@JSQuareD Sleep (200ms)을 사용하는 바쁜 대기가 그다지 많지 않다고 생각 horrible waste하지만 물론 신호가 우수합니다. 또한 Console.ReadLine두 번째 위협에서 무한 루프에서 하나의 차단 호출을 사용 하면 아래의 다른 솔루션과 같이 백그라운드에서 이러한 호출이 많이 발생하는 문제를 방지 할 수 있습니다. 코드를 공유해 주셔서 감사합니다. +1
Roland

2
제 시간에 입력하지 않으면이 방법은 첫 번째 후속 Console.ReadLine()호출 에서 중단되는 것 같습니다 . ReadLine먼저 완료해야하는 "팬텀" 이 표시됩니다.
Derek

1
@Derek 안타깝게도이 방법을 일반 ReadLine 호출과 혼합 할 수 없으며 모든 호출은 Reader를 통해 이루어져야합니다. 이 문제에 대한 해결책은 시간 초과없이 gotInput을 기다리는 리더에 aethod를 추가하는 것입니다. 저는 현재 모바일을 사용하고있어 답변에 쉽게 추가 할 수 없습니다.
JSQuareD

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 ()"가 포함되어 있는데 제대로 작동하지 않습니다. 즉, "up"키를 눌러 이전에 입력 한 명령을 가져 오거나 백 스페이스를 사용하는 등 ReadLine ()의 모든 기능을 잃게됩니다. 화살표 키 등
Contango

9
@ Gravitas : 이것은 작동하지 않습니다. 글쎄, 한 번 작동합니다. 그러나 모든 ReadLine전화는 입력을 기다리고 있습니다. 100 번 호출하면 Enter 키를 100 번 누를 때까지 모두 사라지지 않는 100 개의 스레드가 생성됩니다!
Gabe

2
조심하세요. 이 솔루션은 깔끔하게 보이지만 완료되지 않은 수천 건의 통화가 중단되었습니다. 따라서 반복적으로 호출하면 적합하지 않습니다.
Tom Makin 2012

@Gabe, shakinfree : 솔루션에 대해 여러 호출이 고려되지 않았지만 시간 초과가있는 비동기 호출 하나만 고려되었습니다. 콘솔에 10 개의 메시지를 인쇄 한 다음 각각의 순서로 하나씩 입력하는 것이 사용자에게 혼란 스러울 것 같습니다. 그럼에도 불구하고 매달린 호출의 경우 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

나는 당신이 이것을 본 것이라고 확신합니다. 빠른 google social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/…
Gulzar Nazim

사용자가 아무 작업도하지 않으면이 "시간 초과"가 어떻게되는지 알 수 없습니다. 이 모든 작업은 키를 누르고 다른 로직이 계속 될 때까지 백그라운드에서 로직을 계속 실행하는 것입니다.
mphair

사실, 이것은 수정되어야합니다. 그러나 루프 조건에 타임 아웃을 추가하는 것은 쉽습니다.
Jonathan Allen

KeyAvailable사용자가 ReadLine에 입력을 입력하기 시작했음을 나타내지 만 Enter 키를 누르면 이벤트가 필요하므로 ReadLine이 반환됩니다. 이 솔루션은 ReadKey에서만 작동합니다. 즉, 하나의 문자 만 가져옵니다. 이것은 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 2015 년

2
아름다운! 단순함은 정말로 궁극적 인 정교함입니다. 축하합니다!
BrunoSalvino

10

어떤 식 으로든 두 번째 스레드가 필요합니다. 비동기 IO를 사용하여 자체 선언을 피할 수 있습니다.

  • ManualResetEvent를 선언하고 "evt"라고합니다.
  • System.Console.OpenStandardInput을 호출하여 입력 스트림을 가져옵니다. 데이터를 저장할 콜백 메서드를 지정하고 evt를 설정합니다.
  • 해당 스트림의 BeginRead 메서드를 호출하여 비동기 읽기 작업을 시작합니다.
  • 그런 다음 ManualResetEvent에 시간 제한 대기를 입력하십시오.
  • 대기 시간이 초과되면 읽기를 취소하십시오.

읽기가 데이터를 반환하는 경우 이벤트를 설정하면 기본 스레드가 계속됩니다. 그렇지 않으면 시간 초과 후에도 계속됩니다.


이것은 Accepted Solution이하는 일입니다.
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

보조 스레드를 만들고 콘솔에서 키를 폴링해야한다고 생각합니다. 나는 이것을 달성하는 방법이 없다는 것을 알고 있습니다.


예, 키에 대한 두 번째 스레드 폴링이 있고 앱이 대기하는 동안 앱이 닫히면 해당 키 폴링 스레드는 영원히 대기합니다.
Kelly Elton

실제로 : 두 번째 스레드 또는 "BeginInvoke"가있는 델리게이트 (뒤에서 스레드를 사용합니다. @gp의 답변 참조).
Contango 2011

@ kelton52, 작업 관리자에서 프로세스를 종료하면 보조 스레드가 종료됩니까?
Arlen Beiler 2012-06-09

6

기업 환경에서 완벽하게 작동하는 솔루션을 찾기 전에 5 개월 동안이 문제로 고생했습니다.

지금까지 대부분의 솔루션의 문제점은 Console.ReadLine () 이외의 다른 것에 의존하고 있으며 Console.ReadLine ()에는 많은 이점이 있다는 것입니다.

  • 삭제, 백 스페이스, 화살표 키 등 지원
  • "위로"키를 누르고 마지막 명령을 반복하는 기능 (많이 사용되는 백그라운드 디버깅 콘솔을 구현하는 경우 매우 편리합니다).

내 솔루션은 다음과 같습니다.

  1. Console.ReadLine ()을 사용하여 사용자 입력을 처리 하는 별도의 스레드 를 생성합니다 .
  2. 제한 시간이 지나면 http://inputsimulator.codeplex.com/을 사용하여 [enter] 키를 현재 콘솔 창에 전송하여 Console.ReadLine () 차단을 해제 합니다.

샘플 코드 :

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

Console.ReadLine을 사용하는 스레드를 중단하는 올바른 기술을 포함하여이 기술에 대한 추가 정보 :

.NET 호출을 통해 [enter] 키 입력을 현재 프로세스에 보내는 콘솔 앱은 무엇입니까?

스레드가 Console.ReadLine을 실행할 때 .NET에서 다른 스레드를 중단하는 방법은 무엇입니까?


5

사용자가 'enter'를 누르지 않으면 해당 호출이 반환되지 않기 때문에 대리자에서 Console.ReadLine ()을 호출하는 것은 좋지 않습니다. 델리게이트를 실행하는 스레드는 사용자가 'enter'를 누를 때까지 차단되며 취소 할 방법이 없습니다.

이러한 호출의 시퀀스를 발행하면 예상대로 작동하지 않습니다. 다음을 고려하십시오 (위의 예제 콘솔 클래스 사용).

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

사용자는 첫 번째 프롬프트에 대해 시간 초과가 만료되도록 한 다음 두 번째 프롬프트에 대한 값을 입력합니다. firstName과 lastName은 모두 기본값을 포함합니다. 사용자가 'enter'를 누르면 첫 번째 ReadLine 호출이 완료되지만 코드는 해당 호출을 포기하고 본질적으로 결과를 버렸습니다. 두 번째 ReadLine 호출은 계속 차단되고 시간 초과는 결국 만료되며 반환 된 값이 다시 기본값이됩니다.

BTW- 위 코드에 버그가 있습니다. waitHandle.Close ()를 호출하여 작업자 스레드 아래에서 이벤트를 닫습니다. 타임 아웃이 만료 된 후 사용자가 'enter'를 누르면 작업자 스레드는 ObjectDisposedException을 발생시키는 이벤트에 신호를 보내려고합니다. 예외는 작업자 스레드에서 발생하며 처리되지 않은 예외 처리기를 설정하지 않은 경우 프로세스가 종료됩니다.


1
게시물의 "위"라는 용어가 모호하고 혼란 스럽습니다. 다른 답변을 언급하는 경우 해당 답변에 대한 적절한 링크를 만들어야합니다.
bzlm

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 ()를 사용할 수도 있습니다.
Jamie Kitson

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 프로젝트는 다음과 같은 하나의 클래스가있는 매우 간단한 프로젝트입니다.

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

2
새로운 프로세스에서 별도의 실행 파일을 호출하여 시간이 지정된 ReadLine ()을 수행하는 것은 엄청난 과잉처럼 들립니다. 기본적으로 전체 프로세스를 설정하고 해체하여 ReadLine () 차단 스레드를 중단 할 수없는 문제를 해결하는 것입니다.
bzlm

그런 다음 우리를이 위치에 놓은 Microsoft에 알려주십시오.
Jesse C. Slicer

마이크로 소프트는 당신을 그 위치에 두지 않았습니다. 몇 줄에서 동일한 작업을 수행하는 다른 답변을 살펴보십시오. 위의 코드가 일종의 상을 받아야한다고 생각합니다.하지만 원하는 종류는 아닙니다. :)
Contango

1
아니요, 다른 답변은 OP가 원하는 것을 정확히 수행하지 못했습니다. 그들 모두는 표준 입력 루틴의 기능을 잃거나 모든 요청 Console.ReadLine() 차단되고 다음 요청에서 입력을 보류 한다는 사실에 매달립니다 . 허용되는 답변은 상당히 비슷하지만 여전히 제한이 있습니다.
Jesse C. Slicer 2011 년

1
음, 아니에요. 입력 버퍼는 여전히 차단됩니다 (프로그램이 차단하지 않더라도). 직접 시도해보세요 : 일부 문자를 입력하되 Enter 키는 누르지 마십시오. 시간 초과하십시오. 호출자의 예외를 캡처합니다. 그런 다음 ReadLine()이것을 호출 한 후 프로그램에 다른 것이 있습니다. 무슨 일이 일어나는지보십시오. .NET Framework의 단일 스레드 특성으로 인해 돌아가려면 return 키를 두 번 눌러야합니다 Console. 그것. 그렇지 않습니다. 작업.
Jesse C. Slicer 2011

2

.NET 4는 Tasks를 사용하여 매우 간단하게 만듭니다.

먼저 도우미를 만드세요.

   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 2014

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

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

두 번째 스레드를 얻는 또 다른 저렴한 방법은 대리자로 래핑하는 것입니다.


0

위의 Eric의 게시물 구현 예. 이 특정 예제는 파이프를 통해 콘솔 앱에 전달 된 정보를 읽는 데 사용되었습니다.

 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을 통해 비동기 적으로 호출하는 것은 매우 간단합니다. 저는 표준 취소 메커니즘을 사용하여 작업 비동기 패턴과 그 모든 좋은 것들에 쉽게 연결할 수 있도록했습니다.

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

사용자가 키를 누르고 2000ms 이내에 놓으면 어떨까요?
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 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();
  }    
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.