사용자에게 프롬프트에 응답하는 데 x 초를 주고 싶은 콘솔 앱이 있습니다. 일정 시간이 지나도 입력이 없으면 프로그램 로직이 계속됩니다. 타임 아웃은 빈 응답을 의미한다고 가정합니다.
이에 접근하는 가장 직접적인 방법은 무엇입니까?
답변
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
어쨌든 호출하기 전에 동기화가 이루어져야 합니다.
답변
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();
답변
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);
}
}
답변
이것은 나를 위해 일했습니다.
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);
답변
어떤 식 으로든 두 번째 스레드가 필요합니다. 비동기 IO를 사용하여 자체 선언을 피할 수 있습니다.
- ManualResetEvent를 선언하고 “evt”라고합니다.
- System.Console.OpenStandardInput을 호출하여 입력 스트림을 가져옵니다. 데이터를 저장할 콜백 메서드를 지정하고 evt를 설정합니다.
- 해당 스트림의 BeginRead 메서드를 호출하여 비동기 읽기 작업을 시작합니다.
- 그런 다음 ManualResetEvent에 시간 제한 대기를 입력하십시오.
- 대기 시간이 초과되면 읽기를 취소하십시오.
읽기가 데이터를 반환하는 경우 이벤트를 설정하면 기본 스레드가 계속됩니다. 그렇지 않으면 시간 초과 후에도 계속됩니다.
답변
// 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.
}
답변
보조 스레드를 만들고 콘솔에서 키를 폴링해야한다고 생각합니다. 나는 이것을 달성하는 방법이 없다는 것을 알고 있습니다.
