Me sorprende saber que después de 5 años, todas las respuestas aún sufren uno o más de los siguientes problemas:
- Se utiliza una función distinta de ReadLine, que causa la pérdida de funcionalidad. (Eliminar / retroceso / tecla arriba para la entrada anterior).
- La función se comporta mal cuando se invoca varias veces (genera múltiples subprocesos, muchos ReadLine colgantes o comportamientos inesperados).
- La función se basa en una espera ocupada. Lo cual es un desperdicio horrible ya que se espera que la espera se ejecute en cualquier lugar desde una cantidad de segundos hasta el tiempo de espera, que puede ser de varios minutos. Una espera ocupada que dura tanto tiempo es una horrible cantidad de recursos, lo que es especialmente malo en un escenario de subprocesos múltiples. Si la espera ocupada se modifica con un sueño, esto tiene un efecto negativo en la capacidad de respuesta, aunque admito que esto probablemente no sea un gran problema.
Creo que mi solución resolverá el problema original sin sufrir ninguno de los problemas anteriores:
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.");
}
}
Llamar es, por supuesto, muy 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.");
}
Alternativamente, puede usar la TryXX(out)
convención, como sugirió 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 se llama de la siguiente manera:
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);
En ambos casos, no puede mezclar llamadas Reader
con Console.ReadLine
llamadas normales : si se Reader
agota el tiempo de espera, habrá una ReadLine
llamada pendiente . En cambio, si desea tener una ReadLine
llamada normal (no programada) , simplemente use Reader
y omita el tiempo de espera, de modo que el valor predeterminado sea un tiempo de espera infinito.
Entonces, ¿qué hay de esos problemas de las otras soluciones que mencioné?
- Como puede ver, se usa ReadLine, evitando el primer problema.
- La función se comporta correctamente cuando se invoca varias veces. Independientemente de si se produce un tiempo de espera o no, solo se ejecutará un subproceso en segundo plano y solo como máximo una llamada a ReadLine estará activa. Llamar a la función siempre dará como resultado la última entrada o un tiempo de espera, y el usuario no tendrá que presionar enter más de una vez para enviar su entrada.
- Y, obviamente, la función no se basa en una espera ocupada. En su lugar, utiliza técnicas adecuadas de subprocesamiento múltiple para evitar el desperdicio de recursos.
El único problema que preveo con esta solución es que no es seguro para subprocesos. Sin embargo, varios subprocesos realmente no pueden pedirle al usuario que ingrese al mismo tiempo, por lo que la sincronización debería estar ocurriendo antes de hacer una llamada Reader.ReadLine
.
horrible waste
, pero, por supuesto, su señalización es superior. Además, el uso de unaConsole.ReadLine
llamada de bloqueo en un bucle infinito en una segunda amenaza previene los problemas con muchas de esas llamadas colgadas en segundo plano como en las otras soluciones, muy animadas, a continuación. Gracias por compartir tu código. +1Console.ReadLine()
llamada posterior que realice. Terminas con un "fantasma"ReadLine
que primero debes completar.getInput
.fuente
ReadLine
que llamas se sienta allí esperando la entrada. Si lo llamas 100 veces, crea 100 hilos que no desaparecen hasta que presionas Enter 100 veces.¿Este enfoque utilizando Console.KeyAvailable ayuda?
fuente
KeyAvailable
solo indica que el usuario ha comenzado a escribir entradas en ReadLine, pero necesitamos un evento al presionar Enter, que hace que ReadLine regrese. Esta solución solo funciona para ReadKey, es decir, obtener solo un carácter. Como esto no resuelve la pregunta real para ReadLine, no puedo usar su solución. -1 lo sientoEsto funcionó para mí.
fuente
De una forma u otra necesitas un segundo hilo. Puede usar IO asincrónico para evitar declarar su propio:
Si la lectura devuelve datos, configure el evento y su hilo principal continuará, de lo contrario continuará después del tiempo de espera.
fuente
fuente
Creo que necesitará hacer un hilo secundario y sondear una clave en la consola. No conozco ninguna forma integrada de lograr esto.
fuente
Luché con este problema durante 5 meses antes de encontrar una solución que funcionara perfectamente en un entorno empresarial.
El problema con la mayoría de las soluciones hasta ahora es que dependen de algo distinto de Console.ReadLine () y Console.ReadLine () tiene muchas ventajas:
Mi solución es la siguiente:
Código de muestra:
Más información sobre esta técnica, incluida la técnica correcta para abortar un hilo que usa Console.ReadLine:
Llamada .NET para enviar pulsación de tecla [enter] en el proceso actual, ¿cuál es una aplicación de consola?
¿Cómo abortar otro hilo en .NET, cuando dicho hilo está ejecutando Console.ReadLine?
fuente
Llamar a Console.ReadLine () en el delegado es malo porque si el usuario no presiona 'enter', esa llamada nunca volverá. El hilo que ejecuta al delegado se bloqueará hasta que el usuario presione 'enter', sin forma de cancelarlo.
Emitir una secuencia de estas llamadas no se comportará como cabría esperar. Considere lo siguiente (usando la clase de consola de ejemplo anterior):
El usuario deja que caduque el tiempo de espera para el primer aviso, luego ingresa un valor para el segundo aviso. Tanto firstName como lastName contendrán los valores predeterminados. Cuando el usuario presiona 'enter', la primera llamada de ReadLine se completará, pero el código ha abandonado esa llamada y esencialmente ha descartado el resultado. La segunda llamada ReadLine continuará bloqueándose, el tiempo de espera expirará y el valor devuelto será nuevamente el predeterminado.
Por cierto, hay un error en el código anterior. Al llamar a waitHandle.Close (), cierra el evento por debajo del subproceso de trabajo. Si el usuario presiona 'enter' después de que expire el tiempo de espera, el hilo de trabajo intentará señalar el evento que arroja una ObjectDisposedException. La excepción se genera desde el subproceso de trabajo, y si no ha configurado un controlador de excepciones no controlado, su proceso finalizará.
fuente
Si está en el
Main()
método, no puede usarawait
, por lo que tendrá que usarTask.WaitAny()
:Sin embargo, C # 7.1 presenta la posibilidad de crear un
Main()
método asíncrono , por lo que es mejor usar laTask.WhenAny()
versión siempre que tenga esa opción:fuente
Puede que esté leyendo demasiado la pregunta, pero supongo que la espera sería similar al menú de inicio donde espera 15 segundos a menos que presione una tecla. Puede usar (1) una función de bloqueo o (2) puede usar un hilo, un evento y un temporizador. El evento actuaría como un 'continuar' y se bloquearía hasta que expire el temporizador o se presione una tecla.
El seudocódigo para (1) sería:
fuente
Lamentablemente, no puedo comentar sobre la publicación de Gulzar, pero aquí hay un ejemplo más completo:
fuente
EDITAR : solucionó el problema haciendo que el trabajo real se realizara en un proceso separado y eliminando ese proceso si se agota el tiempo de espera. Ver abajo para más detalles. ¡Uf!
Solo le di una oportunidad y pareció funcionar bien. Mi compañero de trabajo tenía una versión que usaba un objeto Thread, pero creo que el método BeginInvoke () para delegar tipos es un poco más elegante.
El proyecto ReadLine.exe es muy simple y tiene una clase que se ve así:
fuente
Console.ReadLine()
están bloqueando y retrasarán la entrada en la próxima solicitud. La respuesta aceptada es bastante cercana, pero aún tiene limitaciones.ReadLine()
en su programa después de llamar a este. Mira qué pasa. Tienes que presionar return TWICE para que funcione debido a la naturaleza de un solo subprocesoConsole
. Eso. No lo hace Trabajo..NET 4 hace esto increíblemente simple usando Tareas.
Primero, construye tu ayudante:
Segundo, ejecuta con una tarea y espera:
No hay que intentar recrear la funcionalidad de ReadLine ni realizar otros hacks peligrosos para que esto funcione. Las tareas nos permiten resolver la cuestión de una manera muy natural.
fuente
Como si no hubiera suficientes respuestas aquí: 0), lo siguiente encapsula en una solución del método estático @ kwl anterior (la primera).
Uso
fuente
Ejemplo de enhebrado simple para resolver esto
o una cadena estática en la parte superior para obtener una línea completa.
fuente
Soy mi caso, esto funciona bien:
fuente
¿No es esto agradable y corto?
fuente
Este es un ejemplo más completo de la solución de Glen Slayden. Se me ocurrió hacer esto al construir un caso de prueba para otro problema. Utiliza E / S asíncrona y un evento de reinicio manual.
fuente
Mi código se basa completamente en la respuesta del amigo @JSQuareD
Pero necesitaba usar el
Stopwatch
temporizador porque cuando terminé el programaConsole.ReadKey()
todavía estaba esperandoConsole.ReadLine()
y generó un comportamiento inesperado.Funcionó perfectamente para mí. Mantiene la consola original. ReadLine ()
fuente
Otra forma económica de obtener un segundo hilo es envolverlo en un delegado.
fuente
Ejemplo de implementación de la publicación de Eric anterior. Este ejemplo particular se usó para leer la información que se pasó a una aplicación de consola a través de una tubería:
fuente
Tenga en cuenta que si baja por la ruta "Console.ReadKey", pierde algunas de las características interesantes de ReadLine, a saber:
Para agregar un tiempo de espera, modifique el ciclo while para adaptarlo.
fuente
¡No me odies por agregar otra solución a la gran cantidad de respuestas existentes! Esto funciona para Console.ReadKey (), pero podría modificarse fácilmente para que funcione con ReadLine (), etc.
Como los métodos "Console.Read" están bloqueando, es necesario " empujar " la secuencia StdIn para cancelar la lectura.
Sintaxis de llamada:
Código:
fuente
Aquí hay una solución que utiliza
Console.KeyAvailable
. Estas son llamadas de bloqueo, pero debería ser bastante trivial llamarlas de forma asincrónica a través del TPL si lo desea. Utilicé los mecanismos de cancelación estándar para facilitar la conexión con el patrón asincrónico de tareas y todas esas cosas buenas.Hay algunas desventajas con esto.
ReadLine
proporciona (desplazamiento de flecha arriba / abajo, etc.).fuente
Terminé aquí porque se hizo una pregunta duplicada. Se me ocurrió la siguiente solución que parece sencilla. Estoy seguro de que tiene algunos inconvenientes que me perdí.
fuente
Llegué a esta respuesta y terminé haciendo:
fuente
Un ejemplo simple usando
Console.KeyAvailable
:fuente
El código mucho más contemporáneo y basado en tareas se vería así:
fuente
Tuve una situación única de tener una aplicación de Windows (servicio de Windows). Al ejecutar el programa de forma interactiva
Environment.IsInteractive
(VS Debugger o desde cmd.exe), utilicé AttachConsole / AllocConsole para obtener mi stdin / stdout. Para evitar que el proceso finalice mientras se realiza el trabajo, el subproceso de IU llamaConsole.ReadKey(false)
. Quería cancelar la espera que el hilo de la interfaz de usuario estaba haciendo desde otro hilo, así que se me ocurrió una modificación de la solución por @JSquaredD.fuente