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