如何中断Console.ReadLine

dav*_*ooh 20 .net c# console multithreading

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

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

我怎样才能做到这一点?

Han*_*ant 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这非常出色.但是,如果我想再次使用Console.ReadLine(),C#会抛出错误 - 是否有任何方法可以再次重新启用Console.ReadLine()? (2认同)
  • 问一个问题,这不是微不足道的. (2认同)

Con*_*ngo 15

将[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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

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

这个答案优于所有涉及SendKeys和Windows输入模拟器的解决方案,因为它即使当前的应用程序没有焦点也能正常工作.

  • 这在 Windows 10 和 .NET Core 中不再适用。PostMessage 成功,但 ReadKey 未解除阻塞。 (2认同)

小智 5

我需要一个适用于 Mono 的解决方案,因此没有 API 调用。我发布这个只是为了让其他人处于相同的情况,或者想要一种纯粹的 C# 方式来做到这一点。CreateKeyInfoFromInt() 函数是棘手的部分(有些键的长度超过一个字节)。在下面的代码中,如果从另一个线程调用 ReadKeyReset(),则 ReadKey() 将引发异常。下面的代码并不完整,但它展示了使用现有控制台 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;
}
Run Code Online (Sandbox Code Playgroud)


M.k*_*ary 5

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

static IEnumerator<Task<string>> AsyncConsoleInput()
{
    var e = loop(); e.MoveNext(); return e;
    IEnumerator<Task<string>> loop()
    {
        while (true) yield return Task.Run(() => Console.ReadLine());
    }
}

static Task<string> ReadLine(this IEnumerator<Task<string>> console)
{
    if (console.Current.IsCompleted) console.MoveNext();
    return console.Current;
}
Run Code Online (Sandbox Code Playgroud)

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

var console = AsyncConsoleInput();

var task = Task.Run(() =>
{
     // your task on separate thread
});

if (Task.WaitAny(console.ReadLine(), task) == 0) // if ReadLine finished first
{
    task.Wait();
    var x = console.Current.Result; // last user input (await instead of Result in async method)
}
else // task finished first 
{
    var x = console.ReadLine(); // this wont issue another read line because user did not input anything yet. 
}
Run Code Online (Sandbox Code Playgroud)


wis*_*chi 5

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

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

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

我在 Windows 10 上对其进行了测试。它工作得很好,并且比其他解决方案更少“hacky”(例如重新实现 Console.ReadLine,通过 PostMessage 发送返回或关闭句柄,如已接受的答案)

如果网站出现故障,我会在此处引用代码片段:

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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)