如何中断Console.ReadLine

时间:2012-02-28 09:40:40

标签: c# .net multithreading console

是否可以以编程方式停止Console.ReadLine()

我有一个控制台应用程序:大部分逻辑在不同的线程上运行,在主线程中我使用Console.ReadLine()接受输入。当分离的线程停止运行时,我想停止从控制台读取。

我怎样才能做到这一点?

10 个答案:

答案 0 :(得分:18)

更新:此技术在Windows 10上不再可靠。请不要使用它。
在Win10中实施相当繁重的更改,使控制台更像终端。毫无疑问,协助新的Linux子系统。一个(非预期的?)副作用是CloseHandle()死锁,直到读完成,杀死这个方法死了。我会留下原始帖子,只是因为可能帮助某人找到替代方案。


有可能,你必须通过关闭stdin流来颠倒地板垫。该计划证明了这一想法:

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

答案 1 :(得分:14)

将[enter]发送到当前正在运行的控制台应用程序:

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

此代码将[enter]发送到当前控制台进程,中止在Windows内核深处的非托管代码中阻塞的任何ReadLine()调用,这允许C#线程自然退出。

我使用此代码而不是涉及关闭控制台的答案,因为关闭控制台意味着从代码中的该点开始永久禁用ReadLine()和ReadKey()(如果使用它将引发异常)

这个答案优于所有涉及SendKeys和Windows Input Simulator的解决方案,因为即使当前的应用程序没有焦点也能正常工作。

答案 2 :(得分:3)

我需要一个可以与Mono一起使用的解决方案,因此没有API调用。我发布这个只是包装其他任何人处于相同的情况,或者想要一个纯粹的C#方式来做到这一点。 CreateKeyInfoFromInt()函数是棘手的部分(一些键的长度超过一个字节)。在下面的代码中,如果从另一个线程调用ReadKeyReset(),则ReadKey()会抛出异常。下面的代码并不完全完整,但它确实演示了使用现有的Console C#函数创建可插入的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 :(得分:3)

免责声明:这只是复制粘贴的答案。

感谢GéraldBarré提供了如此出色的解决方案:
https://www.meziantou.net/cancelling-console-read.htm

CancelIoEX的文档:
https://docs.microsoft.com/en-us/windows/win32/fileio/cancelioex-func

我在Windows 10上对其进行了测试。与其他解决方案(例如重新实现Console.ReadLine,通过PostMessage发送返回消息或按接受的答案中的方法关闭句柄)相比,它的工作原理很好,并且没有“ hacky”

万一网站崩溃了,我在这里引用代码段:

class Program
{
    const int STD_INPUT_HANDLE = -10;

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr GetStdHandle(int nStdHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CancelIoEx(IntPtr handle, IntPtr lpOverlapped);

    static void Main(string[] args)
    {
        // Start the timeout
        var read = false;
        Task.Delay(10000).ContinueWith(_ =>
        {
            if (!read)
            {
                // Timeout => cancel the console read
                var handle = GetStdHandle(STD_INPUT_HANDLE);
                CancelIoEx(handle, IntPtr.Zero);
            }
        });

        try
        {
            // Start reading from the console
            Console.WriteLine("Do you want to continue [Y/n] (10 seconds remaining):");
            var key = Console.ReadKey();
            read = true;
            Console.WriteLine("Key read");
        }
        // Handle the exception when the operation is canceled
        catch (InvalidOperationException)
        {
            Console.WriteLine("Operation canceled");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Operation canceled");
        }
    }
}

答案 4 :(得分:1)

当前接受的答案不再有效,所以我决定创建一个新答案。唯一安全的方法是创建自己的ReadLine方法我可以想到许多需要这种功能的场景,这里的代码实现了其中一个:

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

将此函数放在代码中的某个位置,这样就可以通过CancellationToken为您提供一个函数,以便我使用更好的代码

using Con = System.Console;

此函数在取消时返回一个空字符串(这对我的情况很好)如果您愿意,可以在上面标记的catch表达式中抛出异常。

同样在同一个catch表达式中,您可以删除stringBuilder.Clear();行,这将导致代码返回到目前为止用户输入的内容。将此与成功或取消的标志相结合,您可以保留到目前为止输入的内容,并在进一步的请求中使用它。

您可以更改的其他事项是,如果您想获得超时功能,可以在循环中设置除取消令牌之外的超时。

我尽量保持干净,但这段代码可以更干净。该方法可以成为async本身,并传入超时和取消令牌。

答案 5 :(得分:1)

我还在寻找一种在某些情况下停止从控制台读取信息的方法。我想到的解决方案是使用这两种方法制作非阻塞版本的读取行。

var animalMovement : Movement<Animal> = CatMovementImpl()

这使我们可以在单独的线程上拥有ReadLine,我们可以等待它或有条件地在其他地方使用它。

CatMovementImpl

答案 6 :(得分:1)

因此,这是在Windows 10上运行且不使用任何奇特的线程或dllimport魔术的解决方案。对我来说效果很好,希望对您有所帮助。

我基本上是在标准输入上创建一个流读取器。读取它有点“异步”,并且如果我想取消读取行,只需处置流读取器即可。

这是我的代码:

textField

注意:是的,我阅读了异步信息,但我等待任务结果,因此它基本上仍在等待用户输入。

答案 7 :(得分:0)

这是Contango答案的修改版本。如果从cmd启动,此代码使用GetForegroundWindow()来获取控制台的MainWindowHandle,而不是使用当前进程的MainWindowhandle。

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

<强>可选

检查前景窗口是否为cmd。如果它不是,那么当前进程应该启动控制台窗口,所以继续使用它。这不重要,因为前景窗口无论如何都应该是当前的流程窗口,但这可以通过仔细检查来帮助您感觉良好。

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

答案 8 :(得分:0)

这将在您的应用程序正在等待Console.Readline()时在单独的线程中处理Ctrl + C:

Console.CancelKeyPress += (_, e) =>
{
    e.Cancel = true;
    Environment.Exit(0);
};

答案 9 :(得分:0)

我刚在GitHub上发现了这个小图书馆:https://github.com/tonerdo/readline

  

ReadLine是类似GNU Readline的库,它是用纯C#构建的。它可以服务   作为内置Console.ReadLine()的替代品,并带来了   以及从unix shell获得的一些终端优势,   例如命令历史记录导航和选项卡自动完成。

     

它是跨平台的,可在支持.NET的任何地方运行,目标是   netstandard1.3表示它可以与.NET Core以及   完整的.NET Framework。

尽管此库在编写本文时不支持中断输入,但对其进行更新应该很简单。另外,这可能是编写自定义解决方案以应对Console.ReadLine局限性的有趣示例。

相关问题