Raymii.org
Quis custodiet ipsos custodes?Home | About | All pages | Cluster Status | RSS Feed
C++ async, threads and user input
Published: 24-04-2020 | Author: Remy van Elst | Text only version of this article
❗ This post is over four years old. It may no longer be up to date. Opinions may have changed.
Table of Contents
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.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!
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 <iostream>
#include <string>
#include <future>
#include <chrono>
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<std::string> 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:
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):
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:
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 <iostream>
#include <memory>
#include <string>
#include <thread>
#include <mutex>
#include <chrono>
#include <atomic>
class Example {
std::atomic<bool> running;
std::atomic<bool> 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<std::mutex> locker(inputMutex);
handleInput();
}
}
void otherThings() {
while (running) {
std::lock_guard<std::mutex> locker(otherThingsMutex);
renderText();
doOtherTask();
}
}
};
int main() {
std::unique_ptr<Example> test = std::make_unique<Example>();
return 0;
}
Tags: articles
, async
, await
, c++
, cmake
, cpp
, development
, future
, stdin
, threads