Fico surpreso ao saber que, após 5 anos, todas as respostas ainda sofrem de um ou mais dos seguintes problemas:
- Uma função diferente de ReadLine é usada, causando perda de funcionalidade. (Delete / backspace / up-key para entrada anterior).
- A função se comporta mal quando invocada várias vezes (gerando vários threads, muitos ReadLine pendurados ou comportamento inesperado).
- A função depende de uma espera ocupada. O que é um desperdício horrível, pois espera-se que a espera seja executada em vários segundos até o tempo limite, o que pode levar vários minutos. Uma espera ocupada, que dura tanto tempo, é uma péssima sucção de recursos, o que é especialmente ruim em um cenário de multithreading. Se a espera ocupada for modificada durante o sono, isso terá um efeito negativo na capacidade de resposta, embora eu admita que esse provavelmente não seja um problema enorme.
Acredito que minha solução resolverá o problema original sem sofrer nenhum dos problemas acima:
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.");
}
}
É claro que ligar é muito fácil:
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.");
}
Como alternativa, você pode usar a TryXX(out)
convenção, como sugeriu 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;
}
Que é chamado da seguinte maneira:
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);
Nos dois casos, você não pode misturar chamadas Reader
com Console.ReadLine
chamadas normais : se o Reader
tempo limite expirar, haverá uma ReadLine
chamada pendente . Em vez disso, se você deseja ter uma ReadLine
chamada normal (não programada) , basta usar Reader
e omitir o tempo limite, para que o padrão seja um tempo limite infinito.
E os problemas das outras soluções que mencionei?
- Como você pode ver, o ReadLine é usado, evitando o primeiro problema.
- A função se comporta corretamente quando invocada várias vezes. Independentemente de um tempo limite ocorrer ou não, apenas um encadeamento em segundo plano estará em execução e apenas no máximo uma chamada para o ReadLine estará ativa. A chamada da função sempre resultará na entrada mais recente ou em um tempo limite, e o usuário não precisará pressionar Enter mais de uma vez para enviar sua entrada.
- E, obviamente, a função não depende de uma espera ocupada. Em vez disso, utiliza técnicas adequadas de multithreading para evitar o desperdício de recursos.
O único problema que eu prevejo com esta solução é que ela não é segura para threads. No entanto, vários encadeamentos não podem realmente solicitar entrada ao usuário ao mesmo tempo; portanto, a sincronização deve ocorrer antes de fazer uma chamada Reader.ReadLine
.