Looking for a better way to cancel on keypress from the console
I'm using the following code to trigger a cancellation token when the user presses a key. I had to use a thread, because some of these operations are long running, so I can't just check for keypresses between async calls. I feel like there has to be a better way than spawning a thread, but it's just not coming to me.
That's an excerpt. The whole code is here: https://pastebin.com/Cpu18pYm
Pastebin
C# monitor keypress console - Pastebin.com
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
24 Replies
adding. i tried using the blocking Read() call to do this but i ran into other complications - like not being able to exit the process cleanly
i think i just thought of a way by manually polling the tasks for completion and checking for keystrokes during the poll rather than using await, but that seems messy
give me a sec.
@honey the codewitch why not use https://learn.microsoft.com/en-us/dotnet/api/system.console.cancelkeypress
Console.CancelKeyPress Event (System)
Occurs when the Control modifier key (Ctrl) and either the C console key (C) or the Break key are pressed simultaneously (Ctrl+C or Ctrl+Break).
i'm not trying to handle the Ctrl+C event
I'm trying to break on an arbitrary keypress in this case
Sure.
But it's a lot simpler, and it also makes it harder to accidentally cancel.
yeah, well consider this a training exercise in using tasks and cancelation tokens - at least that's what I'm treating it as
there are a lot of situations where I could end up in a similar predicament, and I'd love to know the best way to handle it, because this isn't it
fair enough
the thing is,
await
is what's really hamstringing me here. If I was just polling the task for completion in a loop, I could check for keypress in the polling loop and set the cancelation token.
i want something as efficient, but cleaner than that
if such a thing existsokay this was way longer then necessary because I did it withhout reactive.linq but... https://paste.mod.gg/dvfzwhdjpopo/0
BlazeBin - dvfzwhdjpopo
A tool for sharing your source code with the world!
the essence is there is an observable that polls the keyboard for an key, and emits an event if any key is pressed
and there is an observer that triggers a cancelation when it sees any event
I think reactive has some stuff built in that does just this, but I thought I'd try writing it from scratch.
Maybe handling an event instead of polling is possible here
A process can specify a console input buffer handle in one of the wait functions to determine when there is unread console inputYou could see if the stream returned by Console.OpenStandardInput gets only chars or actual keys, streams let you await when reading Nope, that's not for handling keys
awaiting the input stream is still a decent idea though.
actually awaiting the input stream is more ideal for my scenario. that's a good idea
only thing about that @maxmahem is it's doing what i'm doing, but with extra steps
i'd almost rather avoid Task.Run as much as
new Thread
, and maybe ditch await, and poll the tasks for completion to avoid spinning up another threadI know my solution looks like a lot, but thats partially because I did it all from scratch. On the consuming end it can just look like:
or really...
my concern here is partly efficiency. anyone can turn a poll into an event by spinning a thread, but is that really necessary in this case? I don't think it is
well the issue is in this case you are somewhat limited by the interface Console exposes.
basically as i said to someone else above, it's really
await
that hamstrings me here
if i wasn't using await i could poll using while(!Console.KeyAvailable && !task.IsCompleted)
or whatever
the only thing I don't like about that is the mess
i wonder
can i wrap a task using another TaskCompletionSource<> and add a Console.KeyAvailable poll in there somehow? hmmmwell, my solution at least lets you shove the waiting logic elsewhere. You could also do more sophisticated things with the key stream if you wanted.
yeah your solution is flexible, but it's also a bit heavy handed for just monitoring a keypress. like my current solution is
it smells bad to me to spin another thread when i'm waiting on the primary thread doing nothing when i could be polling on the primary thread
i was just hoping there was an elegant, efficient, Task based solution to situations like this
doooh. Tried an await stream solution.. big road block, lol. The stream only sends the next line when you hit enter. Doh. I knew that but forgot.
yeah... the interface here sucks. Just leaves polling really, as all the other methods are blocking with no way to cancel.
also, lol:
Sadly the .net Console doesn't expose any of the general event handling stuff the winapi console has. It looks definitely possible to adapt a "waitable" handle like the one from GetStdHandle(in) to the async model, but the WaitHandle type is a mystery to me
(actually not sure if "console input buffer handle" is the standard input one or a different one from CreateConsoleScreenBuffer, but this doesn't have any other implications)
The whole serial port business is a mess too. I wish .NET was more consistent about its asynchronous APIs
Hmm... when canceling/unsubscribing/disposing of this poll, I wonder if I should wait the task? Just to ensure that the task is completed before I return control.