Thread: Time controlled user-input

  1. #1
    Registered User
    Join Date
    Mar 2016
    Posts
    203

    Time controlled user-input

    user has 5 secs to enter his/her full name from console, else this option is withdrawn and the name entry process becomes different:
    i'm using std::future with std::launch::async and a wait.until(5secs) before checking if std::future::status::ready in which case I call get() on the future, or else we go into the alternative name entry process
    problem is that if user doesn't enter name within 5 secs the thread still continues in the background and the rest of the program becomes undefined. How can I kill this thread there and then once 5 secs are up?
    Code:
    #include <iostream>
    #include <string>
    #include <future>
    #include <chrono>
    
    int main()
    {
    std::string name{};
    
    auto tp = std::chrono::system_clock::now() + std::chrono::seconds(5);
    
    std::future<void> f =
    std::async(std::launch::async, [&name]()
    {
    std::cout << "enter your name \n"; getline(std::cin, name);
    std::cout << "well in time, your name is: " << name << "\n";
    });
    
    std::future_status s = f.wait_until(tp);
    
    if (s == std::future_status::ready)
    {
    f.get();
    }
    else
    {
    std::cout << "took too long, enter first name now \n";
    std::string first_name{};
    getline(std::cin, first_name);
    std::cout << "now enter last name \n";
    std::string last_name{};
    std::cout << "full name is: " << first_name << " " << last_name << "\n";
    }
    }
    my early forays into concurrency so if I'm doing something(s) else very wrong please do not hesitate to let me know. Many thanks
    edit: apologies for the formatting, something went awry between my text-editor and this page
    Last edited by sean_cantab; 04-28-2017 at 10:26 PM.

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    37,485
    > edit: apologies for the formatting, something went awry between my text-editor and this page
    Yeah, it's full of font tags.
    Try "paste as text" or "copy as text" to begin with.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Mar 2016
    Posts
    203
    hope this is better, apologies once again:
    Code:
    #include <iostream>
    #include <string>
    #include <future>
    #include <chrono>
    
    
    int main()
    {
    	std::string name{};
    
    
    	auto tp = std::chrono::system_clock::now() + std::chrono::seconds(5);
    
    
    	std::future<void> f =
    	std::async(std::launch::async, [&name]()
    	{
    		std::cout << "enter your name \n"; getline(std::cin, name);
    		std::cout << "well in time, your name is: " << name << "\n";
    	});
    
    
    	std::future_status s = f.wait_until(tp);
    
    
    	if (s == std::future_status::ready)
    	{
    		f.get();
    	}
    	else
    	{
    		std::cout << "took too long, enter first name now \n";
    		std::string first_name{};
    		getline(std::cin, first_name);
    		std::cout << "now enter last name \n";
    		std::string last_name{};
    		std::cout << "full name is: " << first_name << " " << last_name << "\n";
    	}
    }

  4. #4
    Registered User
    Join Date
    Jun 2015
    Posts
    1,643
    The thread is stuck in the getline call.

    There is no standard way to kill the thread as doing so is obviously not safe since no destructors (or other clean-up code) could be run to free resources.

    To stop a thread safely you would need to use an atomic_bool passed by reference from the controlling thread, test it now and then and exit gracefully if requested. But in your case this would require non-blocking input which doesn't exist in the standard.

    If you really want to kill it you can use a std::thread (instead of future, which is useless in your case anyway), get the underlying thread id (native_handle) and use an OS-specific call to kill it.

    If you want more portability, boost threads offer the ability to kill a thread, but it's still generally unsafe of course.

  5. #5
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    37,485
    Threads are the wrong way to go anyway.

    The console input is line oriented blocking by default.

    Normally, if I were trying to make some kind of per-character interactive program, I would be using ncurses / pdcurses to wrap up the portability issues.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  6. #6
    Registered User
    Join Date
    Mar 2016
    Posts
    203
    algorism, Salem - thanks for your replies, blocking input is indeed the hurdle here and there doesn't seem to be a way around it (at least within my current state of expertise). I started concurrency off the relevant chapter in The C++ Standard Library (2nd ed), Josuttis but that was probably jumping in at the deep end. Since then I've got me hands on Concurrency in Action, Anthony Williams, where the build-up seems more gradual and I'm hoping this will help me understand the subject better, though whether or not it will help me with this particular issue only time shall tell

  7. #7
    Registered User
    Join Date
    Jun 2015
    Posts
    1,643
    It's not that hard to do. If you're on windows you can use the conio.h functions. On linux you can emulate them like this:
    Code:
    // g++ -std=c++11 -Wall -o timeit timeit.cpp -pthread
    
    #define  _BSD_SOURCE  // for cfmakeraw
    #include <string.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include <termios.h>
    
    struct termios orig_termios;
    
    void reset_terminal_mode() {
        tcsetattr(0, TCSANOW, &orig_termios);
    }
    
    void set_conio_terminal_mode() {
        struct termios new_termios;
        tcgetattr(0, &orig_termios);
        memcpy(&new_termios, &orig_termios, sizeof(new_termios));
        cfmakeraw(&new_termios);
        tcsetattr(0, TCSANOW, &new_termios);
    }
    
    int kbhit() {
        struct timeval tv = { 0L, 0L };
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(0, &fds);
        return select(1, &fds, NULL, NULL, &tv);
    }
    
    int getch() {
        int r;
        unsigned char c;
        if ((r = read(0, &c, 1)) < 0)
            return r;
        write(1, &c, 1);
        return c;
    }
    
    
    #include <iostream>
    #include <string>
    #include <future>
    #include <chrono>
    
    int main() {
        std::string name{};
        auto tp = std::chrono::system_clock::now() + std::chrono::seconds(5);
        std::atomic<bool> keep_reading(true);
    
        std::future<void> f =
        std::async(std::launch::async, [&name, &keep_reading]() {
            char s[100], *p = s;
            std::cout << "enter your name \n";
            set_conio_terminal_mode();
            for (int i = 0; keep_reading; ) {
                if (kbhit()) {
                    int c = getch();
                    if (++i == sizeof s || c < 0 || c == '\r') {
                        c = '\n';
                        write(1, &c, 1);
                        *p = '\0';
                        name = s;
                        break;
                    }
                    *p++ = c;
                }
                // You might want to add a millisecond or so of sleep here
                // so the busy wait is not so cpu intensive.
            }
            reset_terminal_mode();
        });
    
        std::future_status s = f.wait_until(tp);
    
        if (s == std::future_status::ready) {
            f.get();
            std::cout << "well in time, your name is: " << name << "\n";
        }
        else {
            keep_reading = false;
            std::cout << "\ntook too long, enter first name now\n";
            getline(std::cin, name);
            std::cout << "name is: " << name << "\n";
        }
    
        return 0;
    }

  8. #8
    Registered User
    Join Date
    Mar 2016
    Posts
    203
    algorism: many thanks for your reply. i need to give it my undivided attention which I should be able to do over the upcoming w/e and will get back to you then. regards, sean

  9. #9
    Registered User
    Join Date
    Mar 2016
    Posts
    203
    algorism: not sure what would be the Windows' equivalents of:
    Code:
    #include <sys/select.h>
    #include <termios.h>
    these would be required to define a windows version of reset_terminal_mode() in your program
    my searches have taken me to Console WinEvents (Windows) but I'm not sure if I'm looking at the right places or, at the very least, in the right directions – apart from this outstanding (and it's a significant outstand no doubt), I've tried to incorporate some of your other suggestions and my current version is looking like this:
    Code:
    #include <iostream>
    #include <string>
    #include <future>
    #include <chrono>
    #include <conio.h>
    
    
    int main()
    {
        using namespace std::chrono_literals;
        std::string name{};
        std::atomic<bool> ready{false};
    
    
        auto tp = std::chrono::system_clock::now() + std::chrono::seconds(5);
    
    
        std::future<void> f =
        std::async(std::launch::async, [&name, &ready]()
        {
            std::cout << "enter your name \n";
            getline(std::cin, name);
            if(kbhit())
            {
                std::this_thread::sleep_for(5ms);
                //small chance of false positive
                ready = true;
            }
            std::cout << "well in time, your name is: " << name << "\n";
        });
    
    
        std::future_status s = f.wait_until(tp);
        if(!ready)
        {
            //reset_terminal_mode();
        }
        if (s == std::future_status::ready)
        {
            f.get();
        }
        else
        {
            std::cout << "took too long, enter first name now \n";
            std::string first_name{};
            getline(std::cin, first_name);
            std::cout << "now enter last name \n";
            std::string last_name{};
            std::cout << "full name is: " << first_name << " " << last_name << "\n";
        }
    }
    If anybody has any Windows-based suggestions on how to wrest control back from console if no input within certain time I'd be very grateful. Thanks

  10. #10
    Registered User
    Join Date
    Jun 2015
    Posts
    1,643
    Your rewrite still uses getline! The whole point is to not use getline, since it will block. You have to use kbhit and getch instead, reading the input one char at a time and, if it's not the newline (or very possibly the carriage return) then append it to name.

    kbhit tells us whether or not a char is in the input buffer. It does not wait (block) but just tells us whether or not one is available at the moment. getch will read the char that we know is there because kbhit told us it was, so it will therefore not block. That's how we avoid blocking.

    It makes me physically sick to use Windows, so I can't test the following code. But try something like this.
    Code:
    #include <iostream>
    #include <string>
    #include <future>
    #include <chrono>
    #include <conio.h>
     
    int main() {
        std::string name;
        auto tp = std::chrono::system_clock::now() + std::chrono::seconds(5);
        std::atomic<bool> keep_reading(true);
     
        std::future<void> f =
        std::async(std::launch::async, [&name, &keep_reading]() {
            std::cout << "enter your name \n";
            for (int i = 0; keep_reading; ) {
                if (kbhit()) {
                    int c = getch();
                    if (c < 0 || c == '\r' || c == '\n')
                        break;
                    name += char(c);
                }
                // You might want to add a millisecond or so of sleep here
                // so the busy wait is not so cpu intensive.
            }
        });
     
        std::future_status s = f.wait_until(tp);
     
        if (s == std::future_status::ready) {
            f.get();
            std::cout << "well in time, your name is: " << name << "\n";
        }
        else {
            keep_reading = false;
            std::cout << "\ntook too long, enter first name now\n";
            getline(std::cin, name);
            std::cout << "name is: " << name << "\n";
        }
     
        return 0;
    }

  11. #11
    Registered User
    Join Date
    Mar 2016
    Posts
    203
    algorism – your program works like a charm, I simply can't thank you enough.
    There's one point though that I'm still not clear about: what is the role of the std::atomic<bool> variable (or perhaps even bool would suffice here as there are no data races?) in this program? Without the atomic<bool> variable, we get to the last else loop in the program and upon data entry main() doesn't return but what's blocking it now since we're no longer using getline()?

  12. #12
    Registered User
    Join Date
    Jun 2015
    Posts
    1,643
    It basically still is "blocking", but we're in control of it now.

    The "keep_reading" loop is going round and round, until keep_reading is false. That's what's keeping the thread alive, but now it's something we can control from the "outside" by setting keep_reading to false. We had no way of influencing the getline blocking (other than actually killing the thread, which is rather brutal).

    There certainly is a "data race" since we have one thread reading a value and another thread modifying that value (setting it to false when it wants the dependent thread to stop).

    And remember that the thread is performing a "busy wait", which means it's sucking on the CPU resource like crazy. It would be good to insert a short sleep where I've indicated. Perhaps Sleep(1) would be appropriate (sleep for 1 millisecond).

  13. #13
    Registered User
    Join Date
    Mar 2016
    Posts
    203
    algorism – when you put it like that the use of the std::atomic<bool> variable makes a lot more sense, thanks.
    I thought I might still be able to avoid using this variable by a call to f.get() just before the program returns – however the program behavior is similar to not using the variable keep_reading viz. it just hangs. I think it is because the shared state of f in this case contains neither a return value nor an exception and so there is nothing to get()?
    And one very last query:
    … it's sucking on the CPU resource like crazy. It would be good to insert a short sleep … for 1 millisecond …
    Wouldn't a longer sleep (say 100ms) free CPU longer while at the same time ensuring that the 5 secs time limit is not crossed erroneously?

  14. #14
    Registered User
    Join Date
    Jun 2015
    Posts
    1,643
    It has nothing to do with there being no return value in the thread function. I assume the get() just waits for the thread to end, which it never will since it's stuck in an infinite loop. You need a way to end the loop, which is what the bool provides.

    A 100ms sleep is probably okay. 1ms is vastly better than none, though, since the loop could potentially be going around many millions of times a second otherwise.

  15. #15
    Registered User
    Join Date
    Mar 2016
    Posts
    203
    many thanks algorism for your help throughout this entire discussion and always. Regards, Sean

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. "real-time" user-input and graphics
    By laczfinador in forum C Programming
    Replies: 4
    Last Post: 03-15-2011, 10:52 AM
  2. time limit for user input
    By dudeomanodude in forum C++ Programming
    Replies: 4
    Last Post: 07-18-2008, 03:01 PM
  3. Idle time of the user
    By cornholio in forum Linux Programming
    Replies: 6
    Last Post: 12-02-2005, 12:51 AM
  4. calculating user time and time elapsed
    By Neildadon in forum C++ Programming
    Replies: 0
    Last Post: 02-10-2003, 06:00 PM

Tags for this Thread