Sign in to follow this  
swiftcoder

Asynchronous console input

Recommended Posts

I am trying to find a way to perform asynchronous input on the command line, as for a chat client (which may receive messages whilst awaiting user input). This is for C++ (though a Python method wouldn't hurt), and the solution needs to be cross-platform. Since I don't know of any way to make std::cin non-blocking, the obvious method seemed to spawn another thread, which reads a line from std::cin in a loop, and communicates it to the server thread over a loackless-queue. This works pretty well, except when it comes to exiting the program - my threading library (boost::thread) provides no way to forcibly kill another thread, and the input thread is blocked on std::cin, so can't receive a message to quit. A call to exit(0) solves this, but it doesn't seem to be the cleanest of solutions. So, my question to you: is there anyway to make std::cin non-blocking, or to test for available data before calling std::getline()?

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Did you try checking std::cin.rdbuf()->in_avail() ?
As I understand it, streambuf.in_avail() only returns the number of characters remaining after a read operation underflows - and the only way to generate an underflow is a blocking read call.

Share this post


Link to post
Share on other sites
If the streambuf input buffer is empty, in_avail() calls showmanyc(), which is used to check if a subsequent call to underflow() will succeed or not.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
If the streambuf input buffer is empty, in_avail() calls showmanyc(), which is used to check if a subsequent call to underflow() will succeed or not.
Nevertheless, it doesn't work - calling in_avail() in a loop always returns 0, no matter how many characters and lines I type at the command prompt. Perhaps the buffering is interfering?

Share this post


Link to post
Share on other sites
No, it's probably because your standard library implementation just doesn't probe stream for input when showmanyc() is called. A lot of the C++ standard is more suggestive than proscriptive.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
No, it's probably because your standard library implementation just doesn't probe stream for input when showmanyc() is called. A lot of the C++ standard is more suggestive than proscriptive.
Right, makes sense. Is multi-threading the only other solution? I mean, multi-threading works, but it seems a little bit like overkill.

Share this post


Link to post
Share on other sites
AFAIK in_avail() and showmanyc() are the only ways to check for input status without a blocking call in standard C++. However, there are other portable alternatives. For example, a curses library like pdcurses can be used to detect if there's keyboard input, though it may not play happily with any buffering done by std::cin.

Share this post


Link to post
Share on other sites
I am sure there are cross platform libraries that can do this. I haven't used it, but something like curses probably has this functionality without worrying about threading. There might be smaller libraries that let you do the same thing too.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
However, there are other portable alternatives. For example, a curses library like pdcurses can be used to detect if there's keyboard input, though it may not play happily with any buffering done by std::cin.
Quote:
Original post by rip-off
I am sure there are cross platform libraries that can do this. I haven't used it, but something like curses probably has this functionality without worrying about threading. There might be smaller libraries that let you do the same thing too.
Ja, curses and friends do expose this functionality. They appear to do this in a fairly naive way, by completely disable buffering for stdin, and emulating line-buffering.

I am not entirely happy with that approach, as curses is a fairly heavy (and unfortunately antiquated) dependency. All of the newer (and lighter) curses replacements seem to depend on curses for this functionality.

Share this post


Link to post
Share on other sites
I personally would just use platform dependent code. Create a function that wraps in_avail() and if it returns 0 then calls the appropriate function.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
I personally would just use platform dependent code. Create a function that wraps in_avail() and if it returns 0 then calls the appropriate function.
Guess I will have to learn how to put Window's command prompt into non-blocking mode - plus it turns out the BSD and Linux termios structures are incompatible [wink]

Share this post


Link to post
Share on other sites
You can't, not really. The console doesn't participate in true async operations like other files/streams, so you have to jury rig something yourself. One option would be to WaitForSingleObject on the console input, with a timeout period and poll the thread's exit condition if the timeout expires. When there is data available, you'll have to peek at the console's input queue to check whether it contains keyboard input, because mouse movements and window activity will also satisfy the wait in default mode.

This might come in handy. The check for keyboard input starts on line 112 and, in your case, hConIn would be the result of GetStdHandle(STD_INPUT_HANDLE). It is pure Win32 but there's nothing stopping you using std::cin once you know there's input available.

Share this post


Link to post
Share on other sites
Quote:
Original post by adeyblue
You can't, not really. The console doesn't participate in true async operations like other files/streams, so you have to jury rig something yourself...

This might come in handy.
Yuk!

The more I search, the more I think that the threading solution is the simplest/best. It is only a few lines of code, works on any platform with thread support, and the input thread will spend its entire lifetime blocked on std::getline(), so it won't consume a noticeable number of CPU cycles.

It looks like this:
#include <boost/thread.hpp>
#include "LocklessQueue.h"

#include <iostream>
#include <string>

LocklessQueue< std::string > commands(16);

struct InputServer
{
void operator () () {
while (1) {
std::string command;

std::getline(std::cin, command);

while (!commands.push(command))
; // spin till the main thread consumes a queue entry
}
}
};

int main() {
InputServer input;
boost::thread inputThread(input);

std::cout << "server launched.\n>> " << std::flush;

while (1) {
std::string command;

if (commands.pop(command)) {

if (command == "status")
std::cout << "server running...\n>> " << std::flush;
else if (command == "quit") {
std::cout << "bye!" << std::endl;
break;
} else {
std::cout << "unrecognised command.\n>> " << std::flush;
}
}
}

exit(0);
}

Share this post


Link to post
Share on other sites
I don't see why threading is such a bad idea. To me it seems like the perfect place to use it. You need to do two things at once: output text, and gather input. Threads allow you to do two things at once (more or less). Therefore, use threads. Can't argue with that logic [grin].

It may be a bit messy that you have to forcibly kill the blocking thread but if it works it works. Maybe you could quit when the user types "quit" or something, and exit the input thread neatly when that happens. Although the user could also quit by closing the console window.

Share this post


Link to post
Share on other sites
Quote:
Original post by XTAL256
It may be a bit messy that you have to forcibly kill the blocking thread but if it works it works.
Yeah, I am probably being overly pedantic. The input thread doesn't carry any heavy resources (just its own stack space), and the OS will cleanup everything as soon as the application exits, so forcibly killing it should be perfectly safe.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this