Я здивований, дізнавшись, що через 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)
конвенцію, як запропонував шмуелі:
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
все-таки здійснити дзвінок .