我惊讶地发现,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
anyway.