This is a text-only version of the following page on https://raymii.org:
---
Title : C++ async, threads and user input
Author : Remy van Elst
Date : 24-04-2020
URL : https://raymii.org/s/articles/Cpp_async_threads_and_user_input.html
Format : Markdown/HTML
---
For an unrelated piece of code, I recently spent a few days trying to figure
out if there was a portable, modern C++ way to handle user input with a
timeout. If there is no input after a few seconds the program can continue
doing other things. TL;DR, there is none, since `stdin` is blocking I/O.
`alarm`, `conio.h`, using `ncurses` or manually polling `stdin` are all way
to complex for the scope of the program. I ended up with using two `std::threads`,
one for input and one for the "other things". I did play with `std::future` and
`std::async` since that way is easier to just 'wait until this is done' as
opposed to manually managing 'actual big boy' threads.
This article has example code that uses `std::async` in an attempt to wait until
the user has given some input and otherwise quit after 5 seconds. It does not
work since `std::getline` is blocking. The `main()` function ends, but the
`async` function is still waiting for user input.
[If you like this article, consider sponsoring me by trying out a Digital Ocean
VPS. With this link you'll get $100 credit for 60 days). (referral link)][99]
[99]: https://www.digitalocean.com/?refcode=7435ae6b8212
At the end of the article I'll also provide the code I ended up using, with the
two threads, one for input and one for 'other work'.
### The async code
Below is a single `test.cpp` file. It is not my actual program, but a simplified
version to show
#include
#include
#include
#include
std::string getString()
{
std::cout << "# getString() start\n";
std::cout << "# getString(): Please enter a string. You have 5 seconds...\n";
std::string input;
std::getline(std::cin, input);
std::cout << "# getString() end\n";
return input;
}
int main()
{
std::cout << "# main() start\n";
std::cout << "# main(): Starting std::async(getString)\n";
std::future futureString = std::async(std::launch::async, getString);
std::cout << "# main(): Waiting 5 seconds for input...\n";
std::chrono::system_clock::time_point five_seconds_passed
= std::chrono::system_clock::now() + std::chrono::seconds(5);
std::future_status status = futureString.wait_until(five_seconds_passed);
if (status == std::future_status::ready)
{
auto result = futureString.get();
std::cout << "# main(): Input result: " << result << "\n";
}
else
{
std::cout << "# main(): Timeout reached... \n";
}
std::cout << "# main() end" << std::endl;
return 0;
}
Make sure you pass `-pthread` while compiling. In CMake:
find_package(Threads REQUIRED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread" )
### The result
The below gif shows the program when input is given within five seconds:
![input][1]
Textual output:
# main() start
# main(): Starting std::async(getString)
# main(): Waiting 5 seconds for input...
# getString() start
# getString(): Please enter a string. You have 5 seconds...
===== RETURN PRESSED ON KEYBOARD =====
# getString() end
# main(): Input result:
# main() end
Process finished with exit code 0
The below gif shows the program when input is not given on time (within 5 sec):
![no input][2]
Textual output:
# main() start
# main(): Starting std::async(getString)
# getString() start
# getString(): Please enter a string. You have 5 seconds...
# main(): Waiting 5 seconds for input...
# main(): Timeout reached...
# main() end
===== RETURN PRESSED ON KEYBOARD =====
# getString() end
Process finished with exit code 0
As you can see, the `async` thread will stay running until the user has given
some input, then the program ends. The timeout we want is sort of available,
the main function does continue. But, stopping the user input thread does not
happen. This, again, is because the `getline()` call is blocking. The thread
will stop after the call is complete. I did try other tricks such as putting
the terminal in nonblocking mode or manually polling with `poll()`, but those
all were not portable (windows/linux) or involved `memcpy` and more `C` like code
than I like, as opposed to modern C++.
### Conclusion and alternative solution
I didn't get to my goal of having user input with a timeout. For the program,
it turns out that a solution with two threads, one for input and one for 'other
work' was a better choice. There is no timeout on the user input, whenever input
is received it is handled and signaled to the main thread. Here below is a
simplified version with a thread that is 'doing work' and one that handles
input. If there is specific input, it does a thing and quits.
Here is a GIF that shows the program:
![thread][3]
Textual output:
Please enter a command:
# (3 seconds pass)
I'm doing other work...
# (3 seconds pass)
I'm doing other work...
# user input is given:
magic
The answer to life, the universe and everything!
Below is the file, single file again as above:
#include
#include
#include
#include
#include
#include
#include
class Example {
std::atomic running;
std::atomic renderedText;
std::mutex inputMutex;
std::mutex otherThingsMutex;
std::thread otherThread;
std::thread inputThread;
void renderText() {
if(!renderedText) {
renderedText = true;
std::cout << "Please enter a command: " << std::endl;
}
}
static void doSomethingWithInput(const std::string& input) {
if (input == "magic")
std::cout << "The answer to life, the universe and everything!" << std::endl;
}
public:
Example() : running(true), renderedText(false),
otherThread(&Example::otherThings, this),
inputThread(&Example::input, this)
{
}
~Example() {
inputThread.join();
otherThread.join();
}
inline void quit() {
running = false;
}
void handleInput() {
std::string input;
std::getline(std::cin, input);
doSomethingWithInput(input);
quit();
}
static void doOtherTask() {
std::cout << "I'm doing other work..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
}
void input() {
while (running) {
std::lock_guard locker(inputMutex);
handleInput();
}
}
void otherThings() {
while (running) {
std::lock_guard locker(otherThingsMutex);
renderText();
doOtherTask();
}
}
};
int main() {
std::unique_ptr test = std::make_unique();
return 0;
}
[1]: /s/inc/img/cpp-timeout-withinput.gif
[2]: /s/inc/img/cpp-timeout-without-input.gif
[3]: /s/inc/img/cpp-timeout-thread.gif
---
License:
All the text on this website is free as in freedom unless stated otherwise.
This means you can use it in any way you want, you can copy it, change it
the way you like and republish it, as long as you release the (modified)
content under the same license to give others the same freedoms you've got
and place my name and a link to this site with the article as source.
This site uses Google Analytics for statistics and Google Adwords for
advertisements. You are tracked and Google knows everything about you.
Use an adblocker like ublock-origin if you don't want it.
All the code on this website is licensed under the GNU GPL v3 license
unless already licensed under a license which does not allows this form
of licensing or if another license is stated on that page / in that software:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Just to be clear, the information on this website is for ment for educational
purposes and you use it at your own risk. I do not take responsibility if you
screw something up. Use common sense, do not rm -rf / as root for example.
If you have any questions then do not hesitate to contact me.
See https://raymii.org/s/static/About.html for details.