I have a console app in which I want to give the user x seconds to respond to the prompt. If no input is made after a certain period of time, program logic should continue. We assume a timeout means empty response.
我有一个控制台应用程序,我想给用户x秒来响应提示符。如果在一段时间后没有输入,程序逻辑应该继续。我们假设超时意味着空响应。
What is the most straightforward way of approaching this?
最直接的方法是什么?
31 个解决方案
#1
91
I'm surprised to learn that after 5 years, all of the answers still suffer from one or more of the following problems:
我很惊讶地发现,5年后,所有的答案仍然存在以下一个或多个问题:
- A function other than ReadLine is used, causing loss of functionality. (Delete/backspace/up-key for previous input).
- 使用非ReadLine的函数,导致功能丢失。(删除/退格/关键以前的输入)。
- Function behaves badly when invoked multiple times (spawning multiple threads, many hanging ReadLine's, or otherwise unexpected behavior).
- 函数在多次调用时表现不佳(生成多个线程、许多挂起的ReadLine或其他意外行为)。
- Function relies on a busy-wait. Which is a horrible waste since the wait is expected to run anywhere from a number of seconds up to the timeout, which might be multiple minutes. A busy-wait which runs for such an ammount of time is a horrible suck of resources, which is especially bad in a multithreading scenario. If the busy-wait is modified with a sleep this has a negative effect on responsiveness, although I admit that this is probably not a huge problem.
- 函数依赖于忙碌的等待。这是一种可怕的浪费,因为等待预计将在任何地方运行,从数秒到超时(可能是数分钟)。运行如此大量的时间的忙碌等待是一种可怕的资源消耗,在多线程场景中尤其糟糕。如果忙碌的等待被一个睡眠改变了,这对响应性有负面影响,尽管我承认这可能不是一个大问题。
I believe my solution will solve the original problem without suffering from any of the above problems:
我相信我的解决方案可以解决最初的问题,不会出现上述任何问题:
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.");
}
}
Calling is, of course, very easy:
当然,打电话很容易:
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.");
}
Alternatively, you can use the TryXX(out)
convention, as shmueli suggested:
您也可以使用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;
}
Which is called as follows:
其名称如下:
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);
In both cases, you cannot mix calls to Reader
with normal Console.ReadLine
calls: if the Reader
times out, there will be a hanging ReadLine
call. Instead, if you want to have a normal (non-timed) ReadLine
call, just use the Reader
and omit the timeout, so that it defaults to an infinite timeout.
在这两种情况下,都不能将对Reader的调用与普通控制台混合。ReadLine调用:如果读取器超时,将会有一个挂起的ReadLine调用。相反,如果您想要一个普通(非定时)的ReadLine调用,只需使用Reader并省略超时,这样它就默认为无限超时。
So how about those problems of the other solutions I mentioned?
那么我提到的其他解决方案的问题呢?
- As you can see, ReadLine is used, avoiding the first problem.
- 如您所见,使用了ReadLine,避免了第一个问题。
- The function behaves properly when invoked multiple times. Regardless of whether a timeout occurs or not, only one background thread will ever be running and only at most one call to ReadLine will ever be active. Calling the function will always result in the latest input, or in a timeout, and the user won't have to hit enter more than once to submit his input.
- 当多次调用时,函数的行为是正确的。无论是否发生超时,都只会运行一个后台线程,并且最多只会激活一次对ReadLine的调用。调用该函数总是会产生最新的输入,或者超时,用户不需要多次按回车键来提交输入。
- And, obviously, the function does not rely on a busy-wait. Instead it uses proper multithreading techniques to prevent wasting resources.
- 而且,显然,这个函数并不依赖于忙碌的等待。相反,它使用适当的多线程技术来防止资源的浪费。
The only problem that I foresee with this solution is that it is not thread-safe. However, multiple threads can't really ask the user for input at the same time, so synchronization should be happening before making a call to Reader.ReadLine
anyway.
我认为这个解决方案唯一的问题是它不是线程安全的。但是,多个线程实际上不能同时请求用户输入,所以在调用Reader之前应该进行同步。ReadLine。
#2
31
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();
#3
26
Will this approach using Console.KeyAvailable help?
这个方法会使用控制台吗?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);
}
}
#4
10
One way or another you do need a second thread. You could use asynchronous IO to avoid declaring your own:
不管怎样,你确实需要第二个线程。您可以使用异步IO避免声明您自己的:
- declare a ManualResetEvent, call it "evt"
- 声明一个ManualResetEvent,称之为“evt”
- call System.Console.OpenStandardInput to get the input stream. Specify a callback method that will store its data and set evt.
- System.Console打电话。OpenStandardInput用于获取输入流。指定一个回调方法,该方法将存储其数据并设置evt。
- call that stream's BeginRead method to start an asynchronous read operation
- 调用流的BeginRead方法来启动异步读取操作
- then enter a timed wait on a ManualResetEvent
- 然后在ManualResetEvent上输入一个定时等待
- if the wait times out, then cancel the read
- 如果等待时间超时,则取消读取
If the read returns data, set the event and your main thread will continue, otherwise you'll continue after the timeout.
如果read返回数据,则设置事件,您的主线程将继续,否则您将在超时后继续。
#5
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.
}
#6
8
I think you will need to make a secondary thread and poll for a key on the console. I know of no built in way to accomplish this.
我认为您需要创建一个辅助线程和轮询控制台上的一个键。据我所知,没有任何方法可以实现这一点。
#7
7
This worked for me.
这为我工作。
ConsoleKeyInfo k = new ConsoleKeyInfo();
Console.WriteLine("Press any key in the next 5 seconds.");
for (int cnt = 5; cnt > 0; cnt--)
{
if (Console.KeyAvailable == true)
{
k = Console.ReadKey();
break;
}
else
{
Console.WriteLine(cnt.ToString());
System.Threading.Thread.Sleep(1000);
}
}
Console.WriteLine("The key pressed was " + k.Key);
#8
6
I struggled with this problem for 5 months before I found an solution that works perfectly in an enterprise setting.
我在这个问题上挣扎了5个月才找到一个在企业环境中完美运行的解决方案。
The problem with most of the solutions so far is that they rely on something other than Console.ReadLine(), and Console.ReadLine() has a lot of advantages:
到目前为止,大多数解决方案的问题是,它们依赖的是除Console.ReadLine()和Console.ReadLine()之外的其他东西。
- Support for delete, backspace, arrow keys, etc.
- 支持删除、退格、箭头键等。
- The ability to press the "up" key and repeat the last command (this comes in very handy if you implement a background debugging console that gets a lot of use).
- 能够按下“up”键并重复最后一个命令(如果您实现了一个非常有用的后台调试控制台,这将非常方便)。
My solution is as follows:
我的解决办法如下:
- Spawn a separate thread to handle the user input using Console.ReadLine().
- 生成一个单独的线程来使用Console.ReadLine()处理用户输入。
- After the timeout period, unblock Console.ReadLine() by sending an [enter] key into the current console window, using http://inputsimulator.codeplex.com/.
- 超时后,通过使用http://inputsimulator.codeplex.com/向当前控制台窗口发送一个[enter]键来解除阻塞Console.ReadLine()。
Sample code:
示例代码:
InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);
More information on this technique, including the correct technique to abort a thread that uses Console.ReadLine:
关于此技术的更多信息,包括终止使用Console.ReadLine的线程的正确技术:
.NET call to send [enter] keystroke into the current process, which is a console app?
.NET调用发送[enter]击键到当前进程,这是一个控制台应用程序?
How to abort another thread in .NET, when said thread is executing Console.ReadLine?
当这个线程正在执行Console.ReadLine时,如何在。net中中止另一个线程?
#9
4
Calling Console.ReadLine() in the delegate is bad because if the user doesn't hit 'enter' then that call will never return. The thread executing the delegate will be blocked until the user hits 'enter', with no way to cancel it.
在委托中调用Console.ReadLine()是不好的,因为如果用户没有点击“enter”,那么调用就不会返回。执行委托的线程将被阻塞,直到用户点击“enter”,无法取消它。
Issuing a sequence of these calls will not behave as you would expect. Consider the following (using the example Console class from above):
发出这些调用的序列不会像您预期的那样运行。考虑以下(使用上面的示例控制台类):
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");
The user lets the timeout expire for the first prompt, then enters a value for the second prompt. Both firstName and lastName will contain the default values. When the user hits 'enter', the first ReadLine call will complete, but the code has abandonded that call and essentially discarded the result. The second ReadLine call will continue to block, the timeout will eventually expire and the value returned will again be the default.
用户让第一个提示符的超时过期,然后为第二个提示符输入一个值。firstName和lastName都包含默认值。当用户点击“enter”时,第一个ReadLine调用将完成,但是代码已经放弃了那个调用,并实质上放弃了结果。第二个ReadLine调用将继续阻塞,超时将最终过期,返回的值将再次成为默认值。
BTW- There is a bug in the code above. By calling waitHandle.Close() you close the event out from under the worker thread. If the user hits 'enter' after the timeout expires, the worker thread will attempt to signal the event which throws an ObjectDisposedException. The exception is thrown from the worker thread, and if you haven't setup an unhandled exception handler your process will terminate.
顺便说一句,上面的代码中有一个错误。通过调用waithandley . close(),您可以从worker线程下关闭事件。如果用户在超时过期后点击“enter”,那么工作线程将尝试向抛出ObjectDisposedException的事件发出信号。异常从工作线程抛出,如果没有设置未处理的异常处理程序,进程将终止。
#10
4
I may be reading too much into the question, but I am assuming the wait would be similar to the boot menu where it waits 15 seconds unless you press a key. You could either use (1) a blocking function or (2) you could use a thread, an event, and a timer. The event would act as a 'continue' and would block until either the timer expired or a key was pressed.
我可能对这个问题读得太多了,但我假设等待会与启动菜单相似,在那里等待15秒,除非你按下一个键。可以使用(1)块函数,也可以使用(2)线程、事件和计时器。事件将作为“continue”,并将阻塞,直到计时器过期或按下键为止。
Pseudo-code for (1) would be:
第(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);
}
#11
4
If you're in the Main()
method, you can't use await
, so you'll have to use Task.WaitAny()
:
如果您处于Main()方法中,您就不能使用等待,因此您将不得不使用Task.WaitAny():
var task = Task.Factory.StartNew(Console.ReadLine);
var result = Task.WaitAny(new Task[] { task }, TimeSpan.FromSeconds(5)) == 0
? task.Result : string.Empty;
However, C# 7.1 introduces the possiblity to create an async Main()
method, so it's better to use the Task.WhenAny()
version whenever you have that option:
然而,c# 7.1引入了创建async 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;
#12
2
I can't comment on Gulzar's post unfortunately, but here's a fuller example:
不幸的是,我无法评论Gulzar的文章,但这里有一个更完整的例子:
while (Console.KeyAvailable == false)
{
Thread.Sleep(250);
i++;
if (i > 3)
throw new Exception("Timedout waiting for input.");
}
input = Console.ReadLine();
#13
2
EDIT: fixed the problem by having the actual work be done in a separate process and killing that process if it times out. See below for details. Whew!
编辑:通过让实际工作在一个单独的进程中完成,并在该进程超时时终止该进程,修复了问题。详情见下文。唷!
Just gave this a run and it seemed to work nicely. My coworker had a version which used a Thread object, but I find the BeginInvoke() method of delegate types to be a bit more elegant.
就这样试了一下,效果还不错。我的同事有一个使用了线程对象的版本,但是我发现委托类型的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);
}
}
}
}
The ReadLine.exe project is a very simple one which has one class which looks like so:
ReadLine。exe项目是一个非常简单的项目,它有一个类,看起来是这样的:
namespace ReadLine
{
internal static class Program
{
private static void Main()
{
System.Console.WriteLine(System.Console.ReadLine());
}
}
}
#14
2
.NET 4 makes this incredibly simple using Tasks.
. net 4让使用任务变得非常简单。
First, build your helper:
首先,建立你的助手:
Private Function AskUser() As String
Console.Write("Answer my question: ")
Return Console.ReadLine()
End Function
Second, execute with a task and wait:
其次,执行任务并等待:
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
There's no trying to recreate ReadLine functionality or performing other perilous hacks to get this working. Tasks let us solve the question in a very natural way.
不需要尝试重新创建ReadLine功能,也不需要执行其他危险的技巧来让它工作。任务让我们以一种非常自然的方式来解决问题。
#15
1
Simple threading example to solve this
简单的线程示例来解决这个问题。
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();
}
or a static string up top for getting an entire line.
或者是一个静态字符串,用于获取整条线。
#16
1
Im my case this work fine:
我的情况很好:
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;
}
#17
1
This is a fuller example of Glen Slayden's solution. I happended to make this when building a test case for another problem. It uses asynchronous I/O and a manual reset event.
这是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!");
}
}
#18
1
As if there weren't already enough answers here :0), the following encapsulates into a static method @kwl's solution above (the first one).
好像这里没有足够的答案: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;
}
Usage
使用
static void Main()
{
Console.WriteLine("howdy");
string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5));
Console.WriteLine("bye");
}
#19
0
Another cheap way to get a 2nd thread is to wrap it in a delegate.
另一种获取第二个线程的廉价方法是将它封装到委托中。
#20
0
Example implementation of Eric's post above. This particular example was used to read information that was passed to a console app via pipe:
以上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
}
}
#21
0
string readline = "?";
ThreadPool.QueueUserWorkItem(
delegate
{
readline = Console.ReadLine();
}
);
do
{
Thread.Sleep(100);
} while (readline == "?");
Note that if you go down the "Console.ReadKey" route, you lose some of the cool features of ReadLine, namely:
注意,如果你沿着“控制台”。“ReadKey”路径,你失去了ReadLine的一些很酷的特性,即:
- Support for delete, backspace, arrow keys, etc.
- 支持删除、退格、箭头键等。
- The ability to press the "up" key and repeat the last command (this comes in very handy if you implement a background debugging console that gets a lot of use).
- 能够按下“up”键并重复最后一个命令(如果您实现了一个非常有用的后台调试控制台,这将非常方便)。
To add a timeout, alter the while loop to suit.
要添加超时,请修改while循环以适应。
#22
0
Isn't this nice and short?
这不是又短又好看吗?
if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout))
{
ConsoleKeyInfo keyInfo = Console.ReadKey();
// Handle keyInfo value here...
}
#23
0
Please don't hate me for adding another solution to the plethora of existing answers! This works for Console.ReadKey(), but could easily be modified to work with ReadLine(), etc.
请不要因为我在现有的众多答案中添加了另一个解决方案而讨厌我!这适用于Console.ReadKey(),但是可以很容易地修改为使用ReadLine()等。
As the "Console.Read" methods are blocking, it's necessary to "nudge" the StdIn stream to cancel the read.
“控制台。读方法正在阻塞,有必要“轻推”StdIn流以取消读。
Calling syntax:
调用的语法:
ConsoleKeyInfo keyInfo;
bool keyPressed = AsyncConsole.ReadKey(500, out keyInfo);
// where 500 is the timeout
Code:
代码:
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);
}
#24
0
Here is a solution that uses Console.KeyAvailable
. These are blocking calls, but it should be fairly trivial to call them asynchronously via the TPL if desired. I used the standard cancellation mechanisms to make it easy to wire in with the Task Asynchronous Pattern and all that good stuff.
这里有一个使用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;
}
}
There are some disadvantages with this.
这有一些缺点。
- You do not get the standard navigation features that
ReadLine
provides (up/down arrow scrolling, etc.). - 您不会得到ReadLine提供的标准导航特性(向上/向下箭头滚动等)。
- This injects '\0' characters into input if a special key is press (F1, PrtScn, etc.). You could easily filter them out by modifying the code though.
- 如果一个特殊的键是按下(F1, PrtScn等),就会将“\0”字符输入到输入中。不过,您可以通过修改代码轻松地过滤掉它们。
#25
0
Ended up here because a duplicate question was asked. I came up with the following solution which looks straightforward. I am sure it has some drawbacks I missed.
结果在这里,因为有人问了一个重复的问题。我提出了下面这个看起来很简单的解决方案。我确信它有我错过的一些缺点。
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;
}
}
#26
0
I came to this answer and end up doing:
我找到了这个答案,最后做了:
/// <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);
}
}
#27
0
A simple example using Console.KeyAvailable
:
使用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");
}
#28
0
Much more contemporary and Task based code would look something like this:
更现代的、基于任务的代码应该是这样的:
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();
}
#29
0
I had a unique situation of having a Windows Application (Windows Service). When running the program interactively Environment.IsInteractive
(VS Debugger or from cmd.exe), I used AttachConsole/AllocConsole to get my stdin/stdout. To keep the process from ending while the work was being done, the UI Thread calls Console.ReadKey(false)
. I wanted to cancel the waiting the UI thread was doing from another thread, so I came up with a modification to the solution by @JSquaredD.
我有一个独特的情况,有一个Windows应用程序(Windows服务)。当运行程序交互环境时。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();
}
}
#30
0
Here is safe solution which fakes console input to unblock thread after timeout. https://github.com/IgoSol/ConsoleReader project provides a sample user dialog implementation.
这里有一个安全的解决方案,它在超时后伪造控制台输入来解锁线程。https://github.com/igosol/comfort ereader项目提供了一个示例用户对话框实现。
var inputLine = ReadLine(5);
public static string ReadLine(uint timeoutSeconds, Func<uint, string> countDownMessage, uint samplingFrequencyMilliseconds)
{
if (timeoutSeconds == 0)
return null;
var timeoutMilliseconds = timeoutSeconds * 1000;
if (samplingFrequencyMilliseconds > timeoutMilliseconds)
throw new ArgumentException("Sampling frequency must not be greater then timeout!", "samplingFrequencyMilliseconds");
CancellationTokenSource cts = new CancellationTokenSource();
Task.Factory
.StartNew(() => SpinUserDialog(timeoutMilliseconds, countDownMessage, samplingFrequencyMilliseconds, cts.Token), cts.Token)
.ContinueWith(t => {
var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
PostMessage(hWnd, 0x100, 0x0D, 9);
}, TaskContinuationOptions.NotOnCanceled);
var inputLine = Console.ReadLine();
cts.Cancel();
return inputLine;
}
private static void SpinUserDialog(uint countDownMilliseconds, Func<uint, string> countDownMessage, uint samplingFrequencyMilliseconds,
CancellationToken token)
{
while (countDownMilliseconds > 0)
{
token.ThrowIfCancellationRequested();
Thread.Sleep((int)samplingFrequencyMilliseconds);
countDownMilliseconds -= countDownMilliseconds > samplingFrequencyMilliseconds
? samplingFrequencyMilliseconds
: countDownMilliseconds;
}
}
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
#1
91
I'm surprised to learn that after 5 years, all of the answers still suffer from one or more of the following problems:
我很惊讶地发现,5年后,所有的答案仍然存在以下一个或多个问题:
- A function other than ReadLine is used, causing loss of functionality. (Delete/backspace/up-key for previous input).
- 使用非ReadLine的函数,导致功能丢失。(删除/退格/关键以前的输入)。
- Function behaves badly when invoked multiple times (spawning multiple threads, many hanging ReadLine's, or otherwise unexpected behavior).
- 函数在多次调用时表现不佳(生成多个线程、许多挂起的ReadLine或其他意外行为)。
- Function relies on a busy-wait. Which is a horrible waste since the wait is expected to run anywhere from a number of seconds up to the timeout, which might be multiple minutes. A busy-wait which runs for such an ammount of time is a horrible suck of resources, which is especially bad in a multithreading scenario. If the busy-wait is modified with a sleep this has a negative effect on responsiveness, although I admit that this is probably not a huge problem.
- 函数依赖于忙碌的等待。这是一种可怕的浪费,因为等待预计将在任何地方运行,从数秒到超时(可能是数分钟)。运行如此大量的时间的忙碌等待是一种可怕的资源消耗,在多线程场景中尤其糟糕。如果忙碌的等待被一个睡眠改变了,这对响应性有负面影响,尽管我承认这可能不是一个大问题。
I believe my solution will solve the original problem without suffering from any of the above problems:
我相信我的解决方案可以解决最初的问题,不会出现上述任何问题:
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.");
}
}
Calling is, of course, very easy:
当然,打电话很容易:
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.");
}
Alternatively, you can use the TryXX(out)
convention, as shmueli suggested:
您也可以使用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;
}
Which is called as follows:
其名称如下:
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);
In both cases, you cannot mix calls to Reader
with normal Console.ReadLine
calls: if the Reader
times out, there will be a hanging ReadLine
call. Instead, if you want to have a normal (non-timed) ReadLine
call, just use the Reader
and omit the timeout, so that it defaults to an infinite timeout.
在这两种情况下,都不能将对Reader的调用与普通控制台混合。ReadLine调用:如果读取器超时,将会有一个挂起的ReadLine调用。相反,如果您想要一个普通(非定时)的ReadLine调用,只需使用Reader并省略超时,这样它就默认为无限超时。
So how about those problems of the other solutions I mentioned?
那么我提到的其他解决方案的问题呢?
- As you can see, ReadLine is used, avoiding the first problem.
- 如您所见,使用了ReadLine,避免了第一个问题。
- The function behaves properly when invoked multiple times. Regardless of whether a timeout occurs or not, only one background thread will ever be running and only at most one call to ReadLine will ever be active. Calling the function will always result in the latest input, or in a timeout, and the user won't have to hit enter more than once to submit his input.
- 当多次调用时,函数的行为是正确的。无论是否发生超时,都只会运行一个后台线程,并且最多只会激活一次对ReadLine的调用。调用该函数总是会产生最新的输入,或者超时,用户不需要多次按回车键来提交输入。
- And, obviously, the function does not rely on a busy-wait. Instead it uses proper multithreading techniques to prevent wasting resources.
- 而且,显然,这个函数并不依赖于忙碌的等待。相反,它使用适当的多线程技术来防止资源的浪费。
The only problem that I foresee with this solution is that it is not thread-safe. However, multiple threads can't really ask the user for input at the same time, so synchronization should be happening before making a call to Reader.ReadLine
anyway.
我认为这个解决方案唯一的问题是它不是线程安全的。但是,多个线程实际上不能同时请求用户输入,所以在调用Reader之前应该进行同步。ReadLine。
#2
31
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();
#3
26
Will this approach using Console.KeyAvailable help?
这个方法会使用控制台吗?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);
}
}
#4
10
One way or another you do need a second thread. You could use asynchronous IO to avoid declaring your own:
不管怎样,你确实需要第二个线程。您可以使用异步IO避免声明您自己的:
- declare a ManualResetEvent, call it "evt"
- 声明一个ManualResetEvent,称之为“evt”
- call System.Console.OpenStandardInput to get the input stream. Specify a callback method that will store its data and set evt.
- System.Console打电话。OpenStandardInput用于获取输入流。指定一个回调方法,该方法将存储其数据并设置evt。
- call that stream's BeginRead method to start an asynchronous read operation
- 调用流的BeginRead方法来启动异步读取操作
- then enter a timed wait on a ManualResetEvent
- 然后在ManualResetEvent上输入一个定时等待
- if the wait times out, then cancel the read
- 如果等待时间超时,则取消读取
If the read returns data, set the event and your main thread will continue, otherwise you'll continue after the timeout.
如果read返回数据,则设置事件,您的主线程将继续,否则您将在超时后继续。
#5
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.
}
#6
8
I think you will need to make a secondary thread and poll for a key on the console. I know of no built in way to accomplish this.
我认为您需要创建一个辅助线程和轮询控制台上的一个键。据我所知,没有任何方法可以实现这一点。
#7
7
This worked for me.
这为我工作。
ConsoleKeyInfo k = new ConsoleKeyInfo();
Console.WriteLine("Press any key in the next 5 seconds.");
for (int cnt = 5; cnt > 0; cnt--)
{
if (Console.KeyAvailable == true)
{
k = Console.ReadKey();
break;
}
else
{
Console.WriteLine(cnt.ToString());
System.Threading.Thread.Sleep(1000);
}
}
Console.WriteLine("The key pressed was " + k.Key);
#8
6
I struggled with this problem for 5 months before I found an solution that works perfectly in an enterprise setting.
我在这个问题上挣扎了5个月才找到一个在企业环境中完美运行的解决方案。
The problem with most of the solutions so far is that they rely on something other than Console.ReadLine(), and Console.ReadLine() has a lot of advantages:
到目前为止,大多数解决方案的问题是,它们依赖的是除Console.ReadLine()和Console.ReadLine()之外的其他东西。
- Support for delete, backspace, arrow keys, etc.
- 支持删除、退格、箭头键等。
- The ability to press the "up" key and repeat the last command (this comes in very handy if you implement a background debugging console that gets a lot of use).
- 能够按下“up”键并重复最后一个命令(如果您实现了一个非常有用的后台调试控制台,这将非常方便)。
My solution is as follows:
我的解决办法如下:
- Spawn a separate thread to handle the user input using Console.ReadLine().
- 生成一个单独的线程来使用Console.ReadLine()处理用户输入。
- After the timeout period, unblock Console.ReadLine() by sending an [enter] key into the current console window, using http://inputsimulator.codeplex.com/.
- 超时后,通过使用http://inputsimulator.codeplex.com/向当前控制台窗口发送一个[enter]键来解除阻塞Console.ReadLine()。
Sample code:
示例代码:
InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);
More information on this technique, including the correct technique to abort a thread that uses Console.ReadLine:
关于此技术的更多信息,包括终止使用Console.ReadLine的线程的正确技术:
.NET call to send [enter] keystroke into the current process, which is a console app?
.NET调用发送[enter]击键到当前进程,这是一个控制台应用程序?
How to abort another thread in .NET, when said thread is executing Console.ReadLine?
当这个线程正在执行Console.ReadLine时,如何在。net中中止另一个线程?
#9
4
Calling Console.ReadLine() in the delegate is bad because if the user doesn't hit 'enter' then that call will never return. The thread executing the delegate will be blocked until the user hits 'enter', with no way to cancel it.
在委托中调用Console.ReadLine()是不好的,因为如果用户没有点击“enter”,那么调用就不会返回。执行委托的线程将被阻塞,直到用户点击“enter”,无法取消它。
Issuing a sequence of these calls will not behave as you would expect. Consider the following (using the example Console class from above):
发出这些调用的序列不会像您预期的那样运行。考虑以下(使用上面的示例控制台类):
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");
The user lets the timeout expire for the first prompt, then enters a value for the second prompt. Both firstName and lastName will contain the default values. When the user hits 'enter', the first ReadLine call will complete, but the code has abandonded that call and essentially discarded the result. The second ReadLine call will continue to block, the timeout will eventually expire and the value returned will again be the default.
用户让第一个提示符的超时过期,然后为第二个提示符输入一个值。firstName和lastName都包含默认值。当用户点击“enter”时,第一个ReadLine调用将完成,但是代码已经放弃了那个调用,并实质上放弃了结果。第二个ReadLine调用将继续阻塞,超时将最终过期,返回的值将再次成为默认值。
BTW- There is a bug in the code above. By calling waitHandle.Close() you close the event out from under the worker thread. If the user hits 'enter' after the timeout expires, the worker thread will attempt to signal the event which throws an ObjectDisposedException. The exception is thrown from the worker thread, and if you haven't setup an unhandled exception handler your process will terminate.
顺便说一句,上面的代码中有一个错误。通过调用waithandley . close(),您可以从worker线程下关闭事件。如果用户在超时过期后点击“enter”,那么工作线程将尝试向抛出ObjectDisposedException的事件发出信号。异常从工作线程抛出,如果没有设置未处理的异常处理程序,进程将终止。
#10
4
I may be reading too much into the question, but I am assuming the wait would be similar to the boot menu where it waits 15 seconds unless you press a key. You could either use (1) a blocking function or (2) you could use a thread, an event, and a timer. The event would act as a 'continue' and would block until either the timer expired or a key was pressed.
我可能对这个问题读得太多了,但我假设等待会与启动菜单相似,在那里等待15秒,除非你按下一个键。可以使用(1)块函数,也可以使用(2)线程、事件和计时器。事件将作为“continue”,并将阻塞,直到计时器过期或按下键为止。
Pseudo-code for (1) would be:
第(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);
}
#11
4
If you're in the Main()
method, you can't use await
, so you'll have to use Task.WaitAny()
:
如果您处于Main()方法中,您就不能使用等待,因此您将不得不使用Task.WaitAny():
var task = Task.Factory.StartNew(Console.ReadLine);
var result = Task.WaitAny(new Task[] { task }, TimeSpan.FromSeconds(5)) == 0
? task.Result : string.Empty;
However, C# 7.1 introduces the possiblity to create an async Main()
method, so it's better to use the Task.WhenAny()
version whenever you have that option:
然而,c# 7.1引入了创建async 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;
#12
2
I can't comment on Gulzar's post unfortunately, but here's a fuller example:
不幸的是,我无法评论Gulzar的文章,但这里有一个更完整的例子:
while (Console.KeyAvailable == false)
{
Thread.Sleep(250);
i++;
if (i > 3)
throw new Exception("Timedout waiting for input.");
}
input = Console.ReadLine();
#13
2
EDIT: fixed the problem by having the actual work be done in a separate process and killing that process if it times out. See below for details. Whew!
编辑:通过让实际工作在一个单独的进程中完成,并在该进程超时时终止该进程,修复了问题。详情见下文。唷!
Just gave this a run and it seemed to work nicely. My coworker had a version which used a Thread object, but I find the BeginInvoke() method of delegate types to be a bit more elegant.
就这样试了一下,效果还不错。我的同事有一个使用了线程对象的版本,但是我发现委托类型的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);
}
}
}
}
The ReadLine.exe project is a very simple one which has one class which looks like so:
ReadLine。exe项目是一个非常简单的项目,它有一个类,看起来是这样的:
namespace ReadLine
{
internal static class Program
{
private static void Main()
{
System.Console.WriteLine(System.Console.ReadLine());
}
}
}
#14
2
.NET 4 makes this incredibly simple using Tasks.
. net 4让使用任务变得非常简单。
First, build your helper:
首先,建立你的助手:
Private Function AskUser() As String
Console.Write("Answer my question: ")
Return Console.ReadLine()
End Function
Second, execute with a task and wait:
其次,执行任务并等待:
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
There's no trying to recreate ReadLine functionality or performing other perilous hacks to get this working. Tasks let us solve the question in a very natural way.
不需要尝试重新创建ReadLine功能,也不需要执行其他危险的技巧来让它工作。任务让我们以一种非常自然的方式来解决问题。
#15
1
Simple threading example to solve this
简单的线程示例来解决这个问题。
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();
}
or a static string up top for getting an entire line.
或者是一个静态字符串,用于获取整条线。
#16
1
Im my case this work fine:
我的情况很好:
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;
}
#17
1
This is a fuller example of Glen Slayden's solution. I happended to make this when building a test case for another problem. It uses asynchronous I/O and a manual reset event.
这是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!");
}
}
#18
1
As if there weren't already enough answers here :0), the following encapsulates into a static method @kwl's solution above (the first one).
好像这里没有足够的答案: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;
}
Usage
使用
static void Main()
{
Console.WriteLine("howdy");
string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5));
Console.WriteLine("bye");
}
#19
0
Another cheap way to get a 2nd thread is to wrap it in a delegate.
另一种获取第二个线程的廉价方法是将它封装到委托中。
#20
0
Example implementation of Eric's post above. This particular example was used to read information that was passed to a console app via pipe:
以上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
}
}
#21
0
string readline = "?";
ThreadPool.QueueUserWorkItem(
delegate
{
readline = Console.ReadLine();
}
);
do
{
Thread.Sleep(100);
} while (readline == "?");
Note that if you go down the "Console.ReadKey" route, you lose some of the cool features of ReadLine, namely:
注意,如果你沿着“控制台”。“ReadKey”路径,你失去了ReadLine的一些很酷的特性,即:
- Support for delete, backspace, arrow keys, etc.
- 支持删除、退格、箭头键等。
- The ability to press the "up" key and repeat the last command (this comes in very handy if you implement a background debugging console that gets a lot of use).
- 能够按下“up”键并重复最后一个命令(如果您实现了一个非常有用的后台调试控制台,这将非常方便)。
To add a timeout, alter the while loop to suit.
要添加超时,请修改while循环以适应。
#22
0
Isn't this nice and short?
这不是又短又好看吗?
if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout))
{
ConsoleKeyInfo keyInfo = Console.ReadKey();
// Handle keyInfo value here...
}
#23
0
Please don't hate me for adding another solution to the plethora of existing answers! This works for Console.ReadKey(), but could easily be modified to work with ReadLine(), etc.
请不要因为我在现有的众多答案中添加了另一个解决方案而讨厌我!这适用于Console.ReadKey(),但是可以很容易地修改为使用ReadLine()等。
As the "Console.Read" methods are blocking, it's necessary to "nudge" the StdIn stream to cancel the read.
“控制台。读方法正在阻塞,有必要“轻推”StdIn流以取消读。
Calling syntax:
调用的语法:
ConsoleKeyInfo keyInfo;
bool keyPressed = AsyncConsole.ReadKey(500, out keyInfo);
// where 500 is the timeout
Code:
代码:
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);
}
#24
0
Here is a solution that uses Console.KeyAvailable
. These are blocking calls, but it should be fairly trivial to call them asynchronously via the TPL if desired. I used the standard cancellation mechanisms to make it easy to wire in with the Task Asynchronous Pattern and all that good stuff.
这里有一个使用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;
}
}
There are some disadvantages with this.
这有一些缺点。
- You do not get the standard navigation features that
ReadLine
provides (up/down arrow scrolling, etc.). - 您不会得到ReadLine提供的标准导航特性(向上/向下箭头滚动等)。
- This injects '\0' characters into input if a special key is press (F1, PrtScn, etc.). You could easily filter them out by modifying the code though.
- 如果一个特殊的键是按下(F1, PrtScn等),就会将“\0”字符输入到输入中。不过,您可以通过修改代码轻松地过滤掉它们。
#25
0
Ended up here because a duplicate question was asked. I came up with the following solution which looks straightforward. I am sure it has some drawbacks I missed.
结果在这里,因为有人问了一个重复的问题。我提出了下面这个看起来很简单的解决方案。我确信它有我错过的一些缺点。
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;
}
}
#26
0
I came to this answer and end up doing:
我找到了这个答案,最后做了:
/// <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);
}
}
#27
0
A simple example using Console.KeyAvailable
:
使用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");
}
#28
0
Much more contemporary and Task based code would look something like this:
更现代的、基于任务的代码应该是这样的:
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();
}
#29
0
I had a unique situation of having a Windows Application (Windows Service). When running the program interactively Environment.IsInteractive
(VS Debugger or from cmd.exe), I used AttachConsole/AllocConsole to get my stdin/stdout. To keep the process from ending while the work was being done, the UI Thread calls Console.ReadKey(false)
. I wanted to cancel the waiting the UI thread was doing from another thread, so I came up with a modification to the solution by @JSquaredD.
我有一个独特的情况,有一个Windows应用程序(Windows服务)。当运行程序交互环境时。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();
}
}
#30
0
Here is safe solution which fakes console input to unblock thread after timeout. https://github.com/IgoSol/ConsoleReader project provides a sample user dialog implementation.
这里有一个安全的解决方案,它在超时后伪造控制台输入来解锁线程。https://github.com/igosol/comfort ereader项目提供了一个示例用户对话框实现。
var inputLine = ReadLine(5);
public static string ReadLine(uint timeoutSeconds, Func<uint, string> countDownMessage, uint samplingFrequencyMilliseconds)
{
if (timeoutSeconds == 0)
return null;
var timeoutMilliseconds = timeoutSeconds * 1000;
if (samplingFrequencyMilliseconds > timeoutMilliseconds)
throw new ArgumentException("Sampling frequency must not be greater then timeout!", "samplingFrequencyMilliseconds");
CancellationTokenSource cts = new CancellationTokenSource();
Task.Factory
.StartNew(() => SpinUserDialog(timeoutMilliseconds, countDownMessage, samplingFrequencyMilliseconds, cts.Token), cts.Token)
.ContinueWith(t => {
var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
PostMessage(hWnd, 0x100, 0x0D, 9);
}, TaskContinuationOptions.NotOnCanceled);
var inputLine = Console.ReadLine();
cts.Cancel();
return inputLine;
}
private static void SpinUserDialog(uint countDownMilliseconds, Func<uint, string> countDownMessage, uint samplingFrequencyMilliseconds,
CancellationToken token)
{
while (countDownMilliseconds > 0)
{
token.ThrowIfCancellationRequested();
Thread.Sleep((int)samplingFrequencyMilliseconds);
countDownMilliseconds -= countDownMilliseconds > samplingFrequencyMilliseconds
? samplingFrequencyMilliseconds
: countDownMilliseconds;
}
}
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);