Comment interrompre la Console.ReadLine

Est-il possible d'arrêter le Console.ReadLine() par programmation?

j'ai une application console: la plupart de la logique fonctionne sur un thread différent et dans le thread principal j'accepte l'entrée en utilisant Console.ReadLine() . J'aimerais arrêter de lire depuis la console quand le thread séparé s'arrête de fonctionner.

Comment puis-je y parvenir?

18
demandé sur davioooh 2012-02-28 13:40:39

5 réponses

mise à jour: cette technique n'est plus fiable sous Windows 10. Ne l'utilisez pas s'il vous plaît.

changements d'implémentation assez lourds dans Win10 pour faire un acte de console plus comme un terminal. Sans doute pour aider dans le nouveau sous-système Linux. Un (involontaire?) L'effet secondaire est que les blocages CloseHandle () jusqu'à ce qu'une lecture soit terminée, tuant cette approche. Je vais laisser le poste original en place, seulement parce que cela pourrait aider quelqu'un pour trouver une alternative.


il est possible, vous devez secouer le tapis de sol en fermant le flux stdin. Ce programme démontre l'idée:

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);
    }
}
17
répondu Hans Passant 2017-01-10 10:25:51

envoyer [enter] à la console app en cours d'exécution:

    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();
        }
    }

ce code envoie [enter] dans le processus courant de la console, en interrompant les appels ReadLine() bloquant dans du code non géré au plus profond du noyau de windows, ce qui permet au fil C# de sortir naturellement.

j'ai utilisé ce code au lieu de la réponse qui implique la fermeture de la console, parce que la fermeture de la console signifie que ReadLine() et ReadKey() sont désactivés de façon permanente de de ce point dans le code (il lève une exception si utilisé).

cette réponse est supérieure à toutes les solutions qui impliquent SendKeys et Windows Input Simulator , car il fonctionne même si l'application actuelle n'a pas la mise au point.

10
répondu Contango 2017-01-10 10:08:13

j'avais besoin d'une solution qui fonctionnerait avec Mono, donc pas d'appels API. Je poste ça juste pour enfermer quelqu'un d'autre est dans la même situation, ou veut une façon pure C# de faire ça. La fonction CreateKeyInfoFromInt () est la partie délicate (certaines touches ont plus d'un octet de longueur). Dans le code ci-dessous, ReadKey() lance une exception si ReadKeyReset() est appelé à partir d'un autre thread. Le code ci-dessous n'est pas tout à fait complet, mais il démontre le concept d'utilisation des fonctions existantes de la Console C# pour créer une fonction d'interuptable GetKey ().

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;
}
3
répondu Josh Guyette 2014-02-12 00:30:52

la réponse actuelle acceptée ne fonctionne plus donc j'ai décidé d'en créer une nouvelle. La seule façon sûre de le faire est de créer votre propre méthode ReadLine je peux penser à de nombreux scénarios nécessitant une telle fonctionnalité et le code ici implémente l'un d'eux:

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();
}

placez cette fonction quelque part dans votre code et cela vous donne une fonction qui peut être annulée via un CancellationToken aussi pour un meilleur code que j'ai utilisé

using Con = System.Console;

cette fonction retourne une chaîne vide lors de l'Annulation (ce qui était bon pour mon cas) vous pouvez jeter une exception à l'intérieur de l'expression marquée catch ci-dessus si vous le souhaitez.

aussi dans la même expression catch vous pouvez supprimer la ligne stringBuilder.Clear(); et cela causera le code de retourner ce que l'Utilisateur a entré jusqu'à présent. Combinez cela avec un succès ou un drapeau annulé et vous pouvez garder ce qui est entré jusqu'à présent et l'utiliser dans d'autres requêtes.

autre chose que vous pouvez changer est que vous pouvez définir un délai en plus du jeton d'annulation dans la boucle si vous voulez obtenir une fonctionnalité de délai.

j'ai essayé d'être aussi propre que j'ai besoin mais ce code peut être plus propre. La méthode peut devenir async elle-même et le jeton de timeout et d'annulation passé.

1
répondu Emad 2018-03-27 11:15:29

C'est une version modifiée de la réponse de Contango. Au lieu d'utiliser la MainWindowhandle du processus courant, ce code utilise GetForegroundWindow() pour obtenir la MainWindowHandle de la Console si elle est lancée depuis 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();
    }
}

facultatif

vérifier si la fenêtre du premier plan était cmd. Si ce n'était pas le cas, alors le processus en cours devrait lancer la fenêtre de la console alors allez-y et utilisez-la. Ce ne devrait pas avoir d'importance parce que la fenêtre de premier plan devrait être la fenêtre de processus en cours de toute façon, mais cela vous aide à vous sentir bien à ce sujet par double vérification.

    int id;
    GetWindowThreadProcessId(ConsoleWindowHnd, out id);
    if (System.Diagnostics.Process.GetProcessById(id).ProcessName != "cmd")
    {
        ConsoleWindowHnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
    }
0
répondu u8it 2017-01-13 23:07:47