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
.
horrible waste
, mas é claro que sua sinalização é superior. Além disso, o uso de umaConsole.ReadLine
chamada de bloqueio em um loop infinito em uma segunda ameaça evita os problemas com muitas dessas chamadas em segundo plano, como nas outras soluções fortemente aprovadas abaixo. Obrigado por compartilhar seu código. 1Console.ReadLine()
chamada subsequente que você fizer. Você acaba com um "fantasma"ReadLine
que precisa ser concluído primeiro.getInput
.fonte
ReadLine
chamadas são feitas esperando por informações. Se você chamá-lo 100 vezes, ele cria 100 threads que nem desaparecem até você pressionar Enter 100 vezes!Essa abordagem usando o Console.KeyAvailable ajuda?
fonte
KeyAvailable
indica apenas que o usuário começou a digitar a entrada no ReadLine, mas precisamos de um evento ao pressionar Enter, que faz com que o ReadLine retorne. Esta solução funciona apenas para o ReadKey, ou seja, obtendo apenas um caractere. Como isso não resolve a questão real do ReadLine, não posso usar sua solução. -1 desculpeIsso funcionou para mim.
fonte
De uma forma ou de outra, você precisa de um segundo segmento. Você pode usar E / S assíncronas para evitar declarar o seu:
Se a leitura retornar dados, defina o evento e seu encadeamento principal continuará; caso contrário, você continuará após o tempo limite.
fonte
fonte
Eu acho que você precisará fazer um thread secundário e pesquisar por uma chave no console. Não conheço nenhuma maneira construída de conseguir isso.
fonte
Eu lutei com esse problema por 5 meses antes de encontrar uma solução que funcionasse perfeitamente em um ambiente corporativo.
O problema com a maioria das soluções até agora é que elas dependem de algo diferente de Console.ReadLine () e Console.ReadLine () tem muitas vantagens:
Minha solução é a seguinte:
Código de amostra:
Mais informações sobre essa técnica, incluindo a técnica correta para abortar um thread que usa Console.ReadLine:
Chamada .NET para enviar pressionamento de tecla [enter] no processo atual, que é um aplicativo de console?
Como abortar outro thread no .NET, quando o referido thread está executando o Console.ReadLine?
fonte
Chamar Console.ReadLine () no delegado é ruim, porque se o usuário não pressionar 'enter', essa chamada nunca retornará. O encadeamento que executa o delegado será bloqueado até o usuário pressionar 'enter', sem nenhuma maneira de cancelá-lo.
A emissão de uma sequência dessas chamadas não se comportará como você esperaria. Considere o seguinte (usando a classe Console de exemplo acima):
O usuário deixa o tempo limite expirar para o primeiro prompt e insere um valor para o segundo prompt. FirstName e lastName conterão os valores padrão. Quando o usuário pressiona 'enter', a primeira chamada ReadLine será concluída, mas o código abandonou essa chamada e essencialmente descartou o resultado. A segunda chamada ReadLine continuará sendo bloqueada, o tempo limite expirará e o valor retornado será o padrão novamente.
Entre - Existe um erro no código acima. Ao chamar waitHandle.Close (), você fecha o evento sob o segmento de trabalho. Se o usuário pressionar 'enter' após o tempo limite expirar, o encadeamento do trabalhador tentará sinalizar o evento que gera uma ObjectDisposedException. A exceção é lançada do encadeamento de trabalho e, se você não tiver configurado um manipulador de exceções sem tratamento, seu processo será encerrado.
fonte
Se você estiver no
Main()
método, não poderá usá-await
lo, portanto precisará usarTask.WaitAny()
:No entanto, o C # 7.1 apresenta a possibilidade de criar um
Main()
método assíncrono ; portanto, é melhor usar aTask.WhenAny()
versão sempre que você tiver essa opção:fonte
Talvez eu esteja lendo muito sobre a questão, mas presumo que a espera seja semelhante ao menu de inicialização, onde aguarda 15 segundos, a menos que você pressione uma tecla. Você pode usar (1) uma função de bloqueio ou (2) você pode usar um encadeamento, um evento e um cronômetro. O evento atuaria como um 'continuar' e seria bloqueado até o tempo expirar ou uma tecla ser pressionada.
O pseudocódigo para (1) seria:
fonte
Infelizmente, não posso comentar no post de Gulzar, mas aqui está um exemplo mais completo:
fonte
EDIT : corrigido o problema executando o trabalho real em um processo separado e eliminando esse processo se o tempo limite expirar. Veja abaixo para detalhes. Uau!
Apenas deu uma corrida e parecia funcionar bem. Meu colega de trabalho tinha uma versão que usava um objeto Thread, mas acho que o método BeginInvoke () dos tipos de delegado é um pouco mais elegante.
O projeto ReadLine.exe é muito simples e possui uma classe com a seguinte aparência:
fonte
Console.ReadLine()
estarem bloqueando e reterão a entrada na próxima solicitação. A resposta aceita é bastante próxima, mas ainda tem limitações.ReadLine()
no seu programa depois de chamar este. Veja o que acontece. Você tem que pressionar return TWICE para fazê-lo funcionar devido à natureza de thread único doConsole
. Isto. Não. Trabalhos.O .NET 4 torna isso incrivelmente simples usando o Tasks.
Primeiro, construa seu ajudante:
Segundo, execute com uma tarefa e aguarde:
Não há como recriar a funcionalidade ReadLine ou executar outros hacks perigosos para que isso funcione. Tarefas, vamos resolver a questão de uma maneira muito natural.
fonte
Como se já não houvesse respostas suficientes aqui: 0), o seguinte é encapsulado na solução de método estático @ kwl acima (a primeira).
Uso
fonte
Exemplo simples de encadeamento para resolver isso
ou uma string estática no topo para obter uma linha inteira.
fonte
No meu caso, este trabalho é bom:
fonte
Isso não é legal e curto?
fonte
Este é um exemplo mais completo da solução de Glen Slayden. Eu fiz isso ao criar um caso de teste para outro problema. Ele usa E / S assíncrona e um evento de redefinição manual.
fonte
Meu código é inteiramente baseado na resposta do amigo @JSQuareD
Mas eu precisava usar o
Stopwatch
timer porque, quando terminei o programa,Console.ReadKey()
ele ainda estava esperandoConsole.ReadLine()
e gerou um comportamento inesperado.Funcionou perfeitamente para mim. Mantém o Console.ReadLine () original
fonte
Outra maneira barata de obter um segundo thread é envolvê-lo em um delegado.
fonte
Exemplo de implementação da postagem de Eric acima. Este exemplo específico foi usado para ler as informações que foram passadas para um aplicativo de console via canal:
fonte
Observe que, se você seguir a rota "Console.ReadKey", perderá alguns dos recursos interessantes do ReadLine, a saber:
Para adicionar um tempo limite, altere o loop while para se adequar.
fonte
Por favor, não me odeie por adicionar outra solução à infinidade de respostas existentes! Isso funciona para Console.ReadKey (), mas pode ser facilmente modificado para funcionar com ReadLine () etc.
Como os métodos "Console.Read" estão bloqueando, é necessário " empurrar " o fluxo StdIn para cancelar a leitura.
Sintaxe de chamada:
Código:
fonte
Aqui está uma solução que usa
Console.KeyAvailable
. Essas são chamadas de bloqueio, mas deve ser bastante trivial chamá-las de forma assíncrona via TPL, se desejado. Usei os mecanismos de cancelamento padrão para facilitar a conexão com o padrão de tarefas assíncronas e todas essas coisas boas.Existem algumas desvantagens nisso.
ReadLine
fornecidos (rolagem de seta para cima / baixo, etc.).fonte
Acabamos aqui porque foi feita uma pergunta duplicada. Eu vim com a seguinte solução, que parece simples. Tenho certeza de que há algumas desvantagens que perdi.
fonte
Cheguei a esta resposta e acabei fazendo:
fonte
Um exemplo simples usando
Console.KeyAvailable
:fonte
Um código muito mais contemporâneo e baseado em tarefas se pareceria com isso:
fonte
Eu tive uma situação única de ter um aplicativo do Windows (Windows Service). Ao executar o programa interativamente
Environment.IsInteractive
(VS Debugger ou no cmd.exe), usei o AttachConsole / AllocConsole para obter meu stdin / stdout. Para impedir que o processo termine enquanto o trabalho estava sendo concluído, o thread da interface do usuário chamaConsole.ReadKey(false)
. Queria cancelar a espera que o thread da interface do usuário estava aguardando de outro thread, então eu vim com uma modificação na solução por @JSquaredD.fonte