with threads thread net multiple example español book c# .net multithreading console

c# - threads - Cómo interrumpir Console.ReadLine



multithreading c# español (5)

¿Es posible detener la Console.ReadLine() programáticamente?

Tengo una aplicación de consola: la mayor parte de la lógica se ejecuta en un subproceso diferente y en el subproceso principal acepto la entrada utilizando Console.ReadLine() . Me gustaría dejar de leer desde la consola cuando el hilo separado deja de ejecutarse.

¿Cómo puedo conseguir esto?


Envía [enter] a la aplicación de la consola que se ejecuta actualmente:

class Program { [DllImport("User32.Dll", EntryPoint = "PostMessageA")] private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam); const int VK_RETURN = 0x0D; const int WM_KEYDOWN = 0x100; static void Main(string[] args) { Console.Write("Switch focus to another window now./n"); ThreadPool.QueueUserWorkItem((o) => { Thread.Sleep(4000); var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; PostMessage(hWnd, WM_KEYDOWN, VK_RETURN, 0); }); Console.ReadLine(); Console.Write("ReadLine() successfully aborted by background thread./n"); Console.Write("[any key to exit]"); Console.ReadKey(); } }

Este código envía [ingrese] al proceso actual de la consola, anulando cualquier bloqueo de las llamadas de la línea de lectura () en el código no administrado dentro del kernel de Windows, lo que permite que el subproceso de C # salga de forma natural.

Utilicé este código en lugar de la respuesta que implica cerrar la consola, porque cerrar la consola significa que ReadLine () y ReadKey () están deshabilitados permanentemente desde ese punto en el código (lanzará una excepción si se usa).

Esta respuesta es superior a todas las soluciones que involucran SendKeys y Windows Input Simulator , ya que funciona incluso si la aplicación actual no tiene el foco.


Esta es una versión modificada de la respuesta de Contango. En lugar de usar la herramienta MainWindowhandle del proceso actual, este código usa GetForegroundWindow () para obtener la MainWindowHandle de la consola si se inicia desde cmd.

using System; using System.Runtime.InteropServices; public class Temp { //Just need this //============================== static IntPtr ConsoleWindowHnd = GetForegroundWindow(); [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); [DllImport("User32.Dll")] private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam); const int VK_RETURN = 0x0D; const int WM_KEYDOWN = 0x100; //============================== public static void Main(string[] args) { System.Threading.Tasks.Task.Run(() => { System.Threading.Thread.Sleep(2000); //And use like this //=================================================== PostMessage(ConsoleWindowHnd, WM_KEYDOWN, VK_RETURN, 0); //=================================================== }); Console.WriteLine("Waiting"); Console.ReadLine(); Console.WriteLine("Waiting Done"); Console.Write("Press any key to continue . . ."); Console.ReadKey(); } }

Opcional

Compruebe si la ventana de primer plano era cmd. Si no fue así, entonces el proceso actual debería iniciar la ventana de la consola, así que adelante, úselo. Esto no debería importar porque la ventana de primer plano debería ser la ventana del proceso actual de todos modos, pero esto te ayuda a sentirte bien al verificar dos veces.

int id; GetWindowThreadProcessId(ConsoleWindowHnd, out id); if (System.Diagnostics.Process.GetProcessById(id).ProcessName != "cmd") { ConsoleWindowHnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; }


La respuesta aceptada actual ya no funciona, así que decidí crear una nueva. La única forma segura de hacerlo es crear su propio método ReadLine . Se me ocurren muchos escenarios que requieren dicha funcionalidad y el código aquí implementa uno de ellos:

public static string CancellableReadLine(CancellationToken cancellationToken) { StringBuilder stringBuilder = new StringBuilder(); Task.Run(() => { try { ConsoleKeyInfo keyInfo; var startingLeft = Con.CursorLeft; var startingTop = Con.CursorTop; var currentIndex = 0; do { var previousLeft = Con.CursorLeft; var previousTop = Con.CursorTop; while (!Con.KeyAvailable) { cancellationToken.ThrowIfCancellationRequested(); Thread.Sleep(50); } keyInfo = Con.ReadKey(); switch (keyInfo.Key) { case ConsoleKey.A: case ConsoleKey.B: case ConsoleKey.C: case ConsoleKey.D: case ConsoleKey.E: case ConsoleKey.F: case ConsoleKey.G: case ConsoleKey.H: case ConsoleKey.I: case ConsoleKey.J: case ConsoleKey.K: case ConsoleKey.L: case ConsoleKey.M: case ConsoleKey.N: case ConsoleKey.O: case ConsoleKey.P: case ConsoleKey.Q: case ConsoleKey.R: case ConsoleKey.S: case ConsoleKey.T: case ConsoleKey.U: case ConsoleKey.V: case ConsoleKey.W: case ConsoleKey.X: case ConsoleKey.Y: case ConsoleKey.Z: case ConsoleKey.Spacebar: case ConsoleKey.Decimal: case ConsoleKey.Add: case ConsoleKey.Subtract: case ConsoleKey.Multiply: case ConsoleKey.Divide: case ConsoleKey.D0: case ConsoleKey.D1: case ConsoleKey.D2: case ConsoleKey.D3: case ConsoleKey.D4: case ConsoleKey.D5: case ConsoleKey.D6: case ConsoleKey.D7: case ConsoleKey.D8: case ConsoleKey.D9: case ConsoleKey.NumPad0: case ConsoleKey.NumPad1: case ConsoleKey.NumPad2: case ConsoleKey.NumPad3: case ConsoleKey.NumPad4: case ConsoleKey.NumPad5: case ConsoleKey.NumPad6: case ConsoleKey.NumPad7: case ConsoleKey.NumPad8: case ConsoleKey.NumPad9: case ConsoleKey.Oem1: case ConsoleKey.Oem102: case ConsoleKey.Oem2: case ConsoleKey.Oem3: case ConsoleKey.Oem4: case ConsoleKey.Oem5: case ConsoleKey.Oem6: case ConsoleKey.Oem7: case ConsoleKey.Oem8: case ConsoleKey.OemComma: case ConsoleKey.OemMinus: case ConsoleKey.OemPeriod: case ConsoleKey.OemPlus: stringBuilder.Insert(currentIndex, keyInfo.KeyChar); currentIndex++; if (currentIndex < stringBuilder.Length) { var left = Con.CursorLeft; var top = Con.CursorTop; Con.Write(stringBuilder.ToString().Substring(currentIndex)); Con.SetCursorPosition(left, top); } break; case ConsoleKey.Backspace: if (currentIndex > 0) { currentIndex--; stringBuilder.Remove(currentIndex, 1); var left = Con.CursorLeft; var top = Con.CursorTop; if (left == previousLeft) { left = Con.BufferWidth - 1; top--; Con.SetCursorPosition(left, top); } Con.Write(stringBuilder.ToString().Substring(currentIndex) + " "); Con.SetCursorPosition(left, top); } else { Con.SetCursorPosition(startingLeft, startingTop); } break; case ConsoleKey.Delete: if (stringBuilder.Length > currentIndex) { stringBuilder.Remove(currentIndex, 1); Con.SetCursorPosition(previousLeft, previousTop); Con.Write(stringBuilder.ToString().Substring(currentIndex) + " "); Con.SetCursorPosition(previousLeft, previousTop); } else Con.SetCursorPosition(previousLeft, previousTop); break; case ConsoleKey.LeftArrow: if (currentIndex > 0) { currentIndex--; var left = Con.CursorLeft - 2; var top = Con.CursorTop; if (left < 0) { left = Con.BufferWidth + left; top--; } Con.SetCursorPosition(left, top); if (currentIndex < stringBuilder.Length - 1) { Con.Write(stringBuilder[currentIndex].ToString() + stringBuilder[currentIndex + 1]); Con.SetCursorPosition(left, top); } } else { Con.SetCursorPosition(startingLeft, startingTop); if (stringBuilder.Length > 0) Con.Write(stringBuilder[0]); Con.SetCursorPosition(startingLeft, startingTop); } break; case ConsoleKey.RightArrow: if (currentIndex < stringBuilder.Length) { Con.SetCursorPosition(previousLeft, previousTop); Con.Write(stringBuilder[currentIndex]); currentIndex++; } else { Con.SetCursorPosition(previousLeft, previousTop); } break; case ConsoleKey.Home: if (stringBuilder.Length > 0 && currentIndex != stringBuilder.Length) { Con.SetCursorPosition(previousLeft, previousTop); Con.Write(stringBuilder[currentIndex]); } Con.SetCursorPosition(startingLeft, startingTop); currentIndex = 0; break; case ConsoleKey.End: if (currentIndex < stringBuilder.Length) { Con.SetCursorPosition(previousLeft, previousTop); Con.Write(stringBuilder[currentIndex]); var left = previousLeft + stringBuilder.Length - currentIndex; var top = previousTop; while (left > Con.BufferWidth) { left -= Con.BufferWidth; top++; } currentIndex = stringBuilder.Length; Con.SetCursorPosition(left, top); } else Con.SetCursorPosition(previousLeft, previousTop); break; default: Con.SetCursorPosition(previousLeft, previousTop); break; } } while (keyInfo.Key != ConsoleKey.Enter); Con.WriteLine(); } catch { //MARK: Change this based on your need. See description below. stringBuilder.Clear(); } }).Wait(); return stringBuilder.ToString(); }

Coloque esta función en algún lugar de su código y le dará una función que se puede cancelar a través de un CancellationToken también para un mejor código que he usado

using Con = System.Console;

Esta función devuelve una cadena vacía en el momento de la cancelación (lo cual fue bueno para mi caso). Si lo desea, puede lanzar una excepción dentro de la expresión de catch marcada arriba.

También en la misma expresión de catch puede eliminar el stringBuilder.Clear(); línea y eso hará que el código devuelva lo que el usuario ingresó hasta ahora. Combine esto con un indicador exitoso o cancelado y puede mantener lo que usó ingresado hasta ahora y usarlo en otras solicitudes.

Otra cosa que puede cambiar es que puede establecer un tiempo de espera además del token de cancelación en el bucle si desea obtener una funcionalidad de tiempo de espera.

Intenté ser tan limpio como lo necesito, pero este código puede ser más limpio. El método puede volverse async y pasar el token de cancelación y el tiempo de espera.


Necesitaba una solución que funcionara con Mono, por lo que no hay llamadas a la API. Estoy publicando este código para que cualquier otra persona se encuentre en la misma situación, o quiera una forma C # pura de hacer esto. La función CreateKeyInfoFromInt () es la parte difícil (algunas teclas tienen más de un byte de longitud). En el código a continuación, ReadKey () lanza una excepción si se llama a ReadKeyReset () desde otro hilo. El código a continuación no está completamente completo, pero demuestra el concepto de usar las funciones existentes de la Consola C # para crear una función de GetKey () interuptable.

static ManualResetEvent resetEvent = new ManualResetEvent(true); /// <summary> /// Resets the ReadKey function from another thread. /// </summary> public static void ReadKeyReset() { resetEvent.Set(); } /// <summary> /// Reads a key from stdin /// </summary> /// <returns>The ConsoleKeyInfo for the pressed key.</returns> /// <param name=''intercept''>Intercept the key</param> public static ConsoleKeyInfo ReadKey(bool intercept = false) { resetEvent.Reset(); while (!Console.KeyAvailable) { if (resetEvent.WaitOne(50)) throw new GetKeyInteruptedException(); } int x = CursorX, y = CursorY; ConsoleKeyInfo result = CreateKeyInfoFromInt(Console.In.Read(), false); if (intercept) { // Not really an intercept, but it works with mono at least if (result.Key != ConsoleKey.Backspace) { Write(x, y, " "); SetCursorPosition(x, y); } else { if ((x == 0) && (y > 0)) { y--; x = WindowWidth - 1; } SetCursorPosition(x, y); } } return result; }


ACTUALIZACIÓN: esta técnica ya no es confiable en Windows 10. No la use, por favor.
Cambios bastante grandes en la implementación en Win10 para hacer que una consola actúe más como un terminal. Sin duda para asistir en el nuevo subsistema Linux. Un efecto colateral (¿no deseado?) Es que CloseHandle () interbloqueos hasta que se completa una lectura, matando a este enfoque muerto. Dejaré la publicación original en su lugar, solo porque podría ayudar a alguien a encontrar una alternativa.

Es posible, tienes que sacudir la alfombra del piso cerrando el flujo estándar. Este programa demuestra la idea:

using System; using System.Threading; using System.Runtime.InteropServices; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { ThreadPool.QueueUserWorkItem((o) => { Thread.Sleep(1000); IntPtr stdin = GetStdHandle(StdHandle.Stdin); CloseHandle(stdin); }); Console.ReadLine(); } // P/Invoke: private enum StdHandle { Stdin = -10, Stdout = -11, Stderr = -12 }; [DllImport("kernel32.dll")] private static extern IntPtr GetStdHandle(StdHandle std); [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr hdl); } }