Я удивлен, узнав, что через 5 лет все ответы по-прежнему страдают одной или несколькими из следующих проблем:
- Используется функция, отличная от ReadLine, что приводит к потере функциональности. (Удалить / backspace / up-key для предыдущего ввода).
- Функция плохо работает при многократном вызове (порождение нескольких потоков, много зависаний 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)
, как предложил Шмуэли:
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. Вызов функции всегда приведет к последнему вводу или к истечению времени ожидания, и пользователю не придется нажимать клавишу ввода более одного раза, чтобы отправить свой ввод.
- И, очевидно, функция не полагается на занятое ожидание. Вместо этого он использует правильные методы многопоточности, чтобы не тратить ресурсы.
Единственная проблема, которую я предвижу с этим решением, заключается в том, что оно не является поточно-ориентированным. Тем не менее, несколько потоков не могут действительно запрашивать ввод данных у пользователя одновременно, поэтому в любом случае должна выполняться синхронизация перед вызовом Reader.ReadLine
.