How to interrupt Console.ReadLine
Is it possible to stop the Console.ReadLine()
programmatically?
I have a console application: the much of the logic runs on a different thread and in the main thread I accept input using Console.ReadLine()
. I'd like to stop reading from console when the separated thread stop running.
How can I achieve this?
UPDATE: this technique is no longer reliable on Windows 10. Don't use it please.
Fairly heavy implementation changes in Win10 to make a console act more like a terminal. No doubt to assist in the new Linux sub-system. One (unintended?) side-effect is that CloseHandle() deadlocks until a read is completed, killing this approach dead. I'll leave the original post in place, only because it might help somebody to find an alternative.
UPDATE2: Look at wischi's answer for a decent alternative.
It's possible, you have to jerk the floor mat by closing the stdin stream. This program demonstrates the 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);
}
}
Send [enter] to the currently running console app:
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();
}
}
This code sends [enter] into the current console process, aborting any ReadLine() calls blocking in unmanaged code deep within the windows kernel, which allows the C# thread to exit naturally.
I used this code instead of the answer that involves closing the console, because closing the console means that ReadLine() and ReadKey() are permanently disabled from that point on in the code (it will throw an exception if its used).
This answer is superior to all solutions that involve SendKeys and Windows Input Simulator, as it works even if the current app does not have the focus.
Disclaimer: This is just a copy & paste answer.
Thanks to Gérald Barré for providing such a great solution:
https://www.meziantou.net/cancelling-console-read.htm
Documentation for CancelIoEX:
https://docs.microsoft.com/en-us/windows/win32/fileio/cancelioex-func
I tested it on Windows 10. It works great and is way less "hacky" than the other solutions (like reimplementing Console.ReadLine, sending return via PostMessage or closing the handle as in the accepted answer)
In case the site goes down I cite the code snippet here:
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");
}
}
}