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)
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输入模拟器的解决方案,因为它即使当前的应用程序没有焦点也能正常工作.
小智 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)
我也在寻找一种在某些情况下停止从控制台读取的方法。我想出的解决方案是用这两种方法制作读取行的非阻塞版本。
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)
免责声明:这只是复制和粘贴答案。
感谢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)