답변:
5 년이 지난 후에도 모든 답변이 여전히 다음 문제 중 하나 이상을 겪고 있다는 사실에 놀랐습니다.
내 솔루션이 위의 문제로 고통받지 않고 원래 문제를 해결할 것이라고 믿습니다.
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하고 시간 제한을 생략하여 기본값이 무한 시간 제한으로 설정되도록합니다.
그렇다면 제가 언급 한 다른 솔루션의 문제는 어떻습니까?
이 솔루션으로 예상되는 유일한 문제는 스레드로부터 안전하지 않다는 것입니다. 그러나 여러 스레드는 실제로 사용자에게 동시에 입력을 요청할 수 없으므로 Reader.ReadLine어쨌든 호출하기 전에 동기화가 이루어져야 합니다.
horrible waste하지만 물론 신호가 우수합니다. 또한 Console.ReadLine두 번째 위협에서 무한 루프에서 하나의 차단 호출을 사용 하면 아래의 다른 솔루션과 같이 백그라운드에서 이러한 호출이 많이 발생하는 문제를 방지 할 수 있습니다. 코드를 공유해 주셔서 감사합니다. +1
Console.ReadLine()호출 에서 중단되는 것 같습니다 . ReadLine먼저 완료해야하는 "팬텀" 이 표시됩니다.
getInput.
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();
ReadLine전화는 입력을 기다리고 있습니다. 100 번 호출하면 Enter 키를 100 번 누를 때까지 모두 사라지지 않는 100 개의 스레드가 생성됩니다!
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);
}
}
KeyAvailable사용자가 ReadLine에 입력을 입력하기 시작했음을 나타내지 만 Enter 키를 누르면 이벤트가 필요하므로 ReadLine이 반환됩니다. 이 솔루션은 ReadKey에서만 작동합니다. 즉, 하나의 문자 만 가져옵니다. 이것은 ReadLine에 대한 실제 질문을 해결하지 못하므로 귀하의 솔루션을 사용할 수 없습니다. -1 죄송합니다
이것은 나를 위해 일했습니다.
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를 사용하여 자체 선언을 피할 수 있습니다.
읽기가 데이터를 반환하는 경우 이벤트를 설정하면 기본 스레드가 계속됩니다. 그렇지 않으면 시간 초과 후에도 계속됩니다.
// 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.
}
보조 스레드를 만들고 콘솔에서 키를 폴링해야한다고 생각합니다. 나는 이것을 달성하는 방법이 없다는 것을 알고 있습니다.
기업 환경에서 완벽하게 작동하는 솔루션을 찾기 전에 5 개월 동안이 문제로 고생했습니다.
지금까지 대부분의 솔루션의 문제점은 Console.ReadLine () 이외의 다른 것에 의존하고 있으며 Console.ReadLine ()에는 많은 이점이 있다는 것입니다.
내 솔루션은 다음과 같습니다.
샘플 코드 :
InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);
Console.ReadLine을 사용하는 스레드를 중단하는 올바른 기술을 포함하여이 기술에 대한 추가 정보 :
사용자가 '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을 발생시키는 이벤트에 신호를 보내려고합니다. 예외는 작업자 스레드에서 발생하며 처리되지 않은 예외 처리기를 설정하지 않은 경우 프로세스가 종료됩니다.
방법에 있다면을 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;
질문을 너무 많이 읽고 있을지 모르지만, 키를 누르지 않으면 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);
}
불행히도 Gulzar의 게시물에 대해 언급 할 수는 없지만 여기에 더 자세한 예가 있습니다.
while (Console.KeyAvailable == false)
{
Thread.Sleep(250);
i++;
if (i > 3)
throw new Exception("Timedout waiting for input.");
}
input = Console.ReadLine();
편집 : 실제 작업을 별도의 프로세스에서 수행하고 시간이 초과되면 해당 프로세스를 종료하여 문제를 해결했습니다. 자세한 내용은 아래를 참조하십시오. 아휴!
이것을 실행하고 잘 작동하는 것처럼 보였습니다. 내 동료는 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());
}
}
}
Console.ReadLine() 이 차단되고 다음 요청에서 입력을 보류 한다는 사실에 매달립니다 . 허용되는 답변은 상당히 비슷하지만 여전히 제한이 있습니다.
ReadLine()이것을 호출 한 후 프로그램에 다른 것이 있습니다. 무슨 일이 일어나는지보십시오. .NET Framework의 단일 스레드 특성으로 인해 돌아가려면 return 키를 두 번 눌러야합니다 Console. 그것. 그렇지 않습니다. 작업.
.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 기능을 다시 만들거나이 작업을 수행하기 위해 다른 위험한 해킹을 수행하려는 시도가 없습니다. 작업을 통해 매우 자연스러운 방식으로 문제를 해결할 수 있습니다.
여기에 충분한 답변이없는 것처럼 : 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");
}
이를 해결하기위한 간단한 스레딩 예제
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();
}
또는 전체 라인을 얻기 위해 상단에 정적 문자열.
내 경우에는 잘 작동합니다.
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;
}
멋지고 짧지 않나요?
if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout))
{
ConsoleKeyInfo keyInfo = Console.ReadKey();
// Handle keyInfo value here...
}
이것은 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!");
}
}
내 코드는 전적으로 친구의 답변 @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.");
}
}
}
}
위의 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
}
}
string readline = "?";
ThreadPool.QueueUserWorkItem(
delegate
{
readline = Console.ReadLine();
}
);
do
{
Thread.Sleep(100);
} while (readline == "?");
"Console.ReadKey"경로로 이동하면 ReadLine의 멋진 기능 중 일부를 잃게됩니다.
시간 제한을 추가하려면 while 루프를 적절하게 변경하십시오.
과다한 기존 답변에 다른 솔루션을 추가하는 것을 싫어하지 마십시오! 이것은 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);
}
다음은 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제공 하는 표준 탐색 기능 (위 / 아래 화살표 스크롤 등)을 사용할 수 없습니다.중복 질문이 있었기 때문에 여기로 끝났습니다. 나는 간단 해 보이는 다음 해결책을 생각 해냈다. 내가 놓친 몇 가지 단점이 있다고 확신합니다.
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;
}
}
나는이 대답에 왔고 결국 :
/// <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);
}
}
훨씬 더 현대적이고 작업 기반 코드는 다음과 같습니다.
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();
}
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();
}
}