-
Notifications
You must be signed in to change notification settings - Fork 8.7k
Description
In my library I expose a terminal input API that looks like this:
public static class Terminal
{
public static ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken = default)
{
// ..
}
}
On Unix, implementing cancellation support was quite easy:
- Create an anonymous pipe at startup.
- When the
CancellationToken
is triggered, write a dummy value to the write end of the pipe. - In
ReadLineAsync
, poll for input on the read end of the pipe andstdin
. - If the poll returns indicating input on the pipe, drain the pipe and throw
OperationCanceledException
. - If the poll returns indicating input on
stdin
, actually read the data and return it.
(For reference, the implementation of the above can be found here and here.)
I would now like to implement something similar for Windows. Unfortunately, as far as I can tell, this is bordering on impossible at the moment.
I naïvely tried to replicate the approach above on Windows, only to discover that WaitForMultipleObjects
does not support polling on pipes. I replaced the pipe with an event since WaitForMultipleObjects
supports those. I then ran into another problem: Polling on a console input handle will return when any INPUT_RECORD
arrives, not just KEY_EVENT_RECORD
s. I decided to try CancelIo
and CancelSynchronousIo
on the off chance that they'd work instead of polling, but they just don't work on console input, apparently. At that point, things got hairy; I went back to the polling approach and tried to inspect the input buffer looking for KEY_EVENT_RECORD
s specifically, discarding other event records, and then resuming the wait if there are no KEY_EVENT_RECORD
s. Besides being very gross and hacky, this fell apart quickly as it turns out there's no clean way to figure out if a given KEY_EVENT_RECORD
(or a series of them) will actually result in text input being available on the next ReadConsole
call. Worse yet, even when I did hack together some heuristics, the behavior turned out to be different between CMD and Windows Terminal. At that point, I gave up and ripped out Windows cancellation support.
So I suppose this issue boils down to me asking: Is there a way to achieve what I'm trying to do that I just haven't realized yet? If not, could one be implemented?