3

Is there a way to get a Task that completes and returns a key press without a dedicated thread?

// Kernel callback, not a new thread in my process waiting for a keyboard event
var key = await KeyPress();

As Console.ReadKey() is a blocking call and uses a thread only to wait for user input.

user4388177
  • 2,433
  • 3
  • 16
  • 30
  • 1
    Like this? https://stackoverflow.com/questions/5620603/non-blocking-read-from-standard-i-o-in-c-sharp/5620647#5620647 – mxmissile Jul 18 '17 at 16:52
  • @mxmissile one thread is still busy looping and running ReadKey – user4388177 Jul 18 '17 at 18:05
  • Are you not OK with having a dedicated (yet single) thread that'd be blocking waiting for console input? Then you can use something like consumer/producer pattern to asynchronously consume the console input on other threads. – noseratio Jul 19 '17 at 02:44
  • @Noseratio that is the thing, a thread dedicated only to wait for user input sounds like a waste (not necessarily a big one, but it feels like it should have an implementation for this). – user4388177 Jul 19 '17 at 06:04

2 Answers2

2

You can open the standard input stream, which has asynchronous operations for reading:

using (var stream = Console.OpenStandardInput())
{
    var buffer = new byte[1];
    var bytesRead = await stream.ReadAsync(buffer, 0, 1);
    char c = (char)buffer[0];
    Console.WriteLine(c);
}
Servy
  • 202,030
  • 26
  • 332
  • 449
0

that is the thing, a thread dedicated only to wait for user input sounds like a waste (not necessarily a big one, but it feels like it should have an implementation for this).

I wouldn't be concerned about this for a console app that expects user input.

Anyhow, it might be possible to achieve what you're after by using some underlying Win32 APIs. The docs for ReadConsoleInput say:

A process can specify a console input buffer handle in one of the wait functions to determine when there is unread console input. When the input buffer is not empty, the state of a console input buffer handle is signaled. To determine the number of unread input records in a console's input buffer, use the GetNumberOfConsoleInputEvents function. To read input records from a console input buffer without affecting the number of unread records, use the PeekConsoleInput function. To discard all unread records in a console's input buffer, use the FlushConsoleInputBuffer function.

So, in theory, you could use a handle returned by GetStdHandle and pass it to RegisterWaitForSingleObject. Then you could convert it to an awaitable task using TaskCompletionSource, e.g. as described here.

I haven't verified this in practice. It shoudn't be blocking a thread, but IMO, again, the game isn't worth the candle.

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • [Console.ReadKey](http://referencesource.microsoft.com/#mscorlib/system/console.cs,1476) uses ReadConsoleInput itself *with* a lock to prevent multiple threads from calling it concurrently. A correct implementation would have to replicate that – Panagiotis Kanavos Jul 19 '17 at 07:06
  • @PanagiotisKanavos, not sure I understood your point. The OP doesn't need to `Console.ReadKey` (or `ReadConsoleInput`) to await for the input asynchronously. He just need the std input handle and to register a callack with `RegisterWaitForSingleObject`. Then - once he's got notified of the available input - he can use `ReadKey` to read it on whatever thread he currently is. As long as he doesn't call `ReadKey` from multple threads semultaneosly, I suppose. – noseratio Jul 19 '17 at 07:18
  • 1
    @Noseratio that is likely to be the solution, I will try and implement it and give some feedback. I was exactly looking at some kernel callback to wrap in a TaskCompletionSource. – user4388177 Jul 19 '17 at 08:24
  • Finally had a chance to try it; works, but the documentation of `RegisterWaitForSingleObject` states: "Directs a wait thread in the thread pool to wait on the object. The wait thread queues the specified callback function to the thread pool when one of the following occurs" so it will block a thread anyway I presume. Just in a more fancy and complex way. EDIT: Maybe I'm wrong, a wait thread is something different... – user4388177 Jul 30 '17 at 18:36
  • 1
    The experiment works, but the handle gets signaled for so many events beside the keyboard input that it is actually firing up even MORE threads degrading performances. I will mark your answer as correct as it was quite useful and in theory does the trick. – user4388177 Jul 30 '17 at 20:35
  • @user4388177, thanks for an update. `RegisterWaitForSingleObject` does block a thread but it does it cummulatively for up to as many as 63 handles. – noseratio Jul 30 '17 at 23:33
  • @Noseratio interesting, thank you. Indeed it looks like a so called "wait thread" is just a regular thread ( mentioned here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms685061%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 ) but definitely good it handles multiple handles. – user4388177 Jul 31 '17 at 11:27