non-blocking std::getline, exit if no input
Asked Answered
S

3

15

Currently I have a program that reads from the standard input, occasionally the program needs to just keep running if no input is made, usually this is a test script there is no 'enter' so to speak.

program -v1 -v2 -v3 <input >output

v1 - v3 are command line arguments respectively

Basically the program spits out the command line arguments and their respective meaning to the program if no 'input' is given and then should exit.

However at the moment if give it an empty test file or just run without pressing enter after running it blocks on the std::getline I use to input the commands.

while(std::getline(std::cin,foo)
{do stuff}

where foo is a string.

How do I get it to just run through and do stuff at least once then exit in the event of no input? In the event of input the do stuff occurs once for every line in standard input.

Would a switch to a do-while loop, with a check pre loop as to whether it's got any input, work?

Something like

if cin empty
set flag

do
 {do stuff
 check flag}
while(getline)

or is non-blocking io not possible in c++?

This question seems to be rehashed over and over but I couldn't find a definitive answer or even an answer that was platform agnostic(this program is academic in nature, coded on windows and tested on unix).

Swerve answered 16/5, 2013 at 16:4 Comment(3)
so are you saying you want the loop to run once no matter what, then exit if no input is given before a getline call?Cattery
possible duplicate of checking data availability before calling std::getline Unfortunately, there probably isn't a portable way to do this in standard C++.Deaconess
Can you use some low-level functions from C?Eyeless
C
13

Using std::cin asynchronously might be the only way to make this work, as iostream is not designed to have non-blocking behavior. Here is an example:

Async Example 1 Async Example 2 (printing a space out every 1/10th of a second while accepting CLI input at the same time)

The code is commented so it should be easy to understand. It's a thread-safe class that lets you asynchronously get a line using std::cin.

Very easy to use for asynchronous CLI getline purposes, with 0% CPU usage on my computer. It works perfectly well on Windows 10 in Visual Studio 2015 c++ Win32 Console Debug and Release mode. If it doesn't work in your OS or environment, that's too bad.

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <atomic>

using namespace std;

//This code works perfectly well on Windows 10 in Visual Studio 2015 c++ Win32 Console Debug and Release mode.
//If it doesn't work in your OS or environment, that's too bad; guess you'll have to fix it. :(
//You are free to use this code however you please, with one exception: no plagiarism!
//(You can include this in a much bigger project without giving any credit.)
class AsyncGetline
{
    public:
        //AsyncGetline is a class that allows for asynchronous CLI getline-style input
        //(with 0% CPU usage!), which normal iostream usage does not easily allow.
        AsyncGetline()
        {
            input = "";
            sendOverNextLine = true;
            continueGettingInput = true;

            //Start a new detached thread to call getline over and over again and retrieve new input to be processed.
            thread([&]()
            {
                //Non-synchronized string of input for the getline calls.
                string synchronousInput;
                char nextCharacter;

                //Get the asynchronous input lines.
                do
                {
                    //Start with an empty line.
                    synchronousInput = "";

                    //Process input characters one at a time asynchronously, until a new line character is reached.
                    while (continueGettingInput)
                    {
                        //See if there are any input characters available (asynchronously).
                        while (cin.peek() == EOF)
                        {
                            //Ensure that the other thread is always yielded to when necessary. Don't sleep here;
                            //only yield, in order to ensure that processing will be as responsive as possible.
                            this_thread::yield();
                        }

                        //Get the next character that is known to be available.
                        nextCharacter = cin.get();

                        //Check for new line character.
                        if (nextCharacter == '\n')
                        {
                            break;
                        }

                        //Since this character is not a new line character, add it to the synchronousInput string.
                        synchronousInput += nextCharacter;
                    }

                    //Be ready to stop retrieving input at any moment.
                    if (!continueGettingInput)
                    {
                        break;
                    }

                    //Wait until the processing thread is ready to process the next line.
                    while (continueGettingInput && !sendOverNextLine)
                    {
                        //Ensure that the other thread is always yielded to when necessary. Don't sleep here;
                        //only yield, in order to ensure that the processing will be as responsive as possible.
                        this_thread::yield();
                    }

                    //Be ready to stop retrieving input at any moment.
                    if (!continueGettingInput)
                    {
                        break;
                    }

                    //Safely send the next line of input over for usage in the processing thread.
                    inputLock.lock();
                    input = synchronousInput;
                    inputLock.unlock();

                    //Signal that although this thread will read in the next line,
                    //it will not send it over until the processing thread is ready.
                    sendOverNextLine = false;
                }
                while (continueGettingInput && input != "exit");
            }).detach();
        }

        //Stop getting asynchronous CLI input.
        ~AsyncGetline()
        {
            //Stop the getline thread.
            continueGettingInput = false;
        }

        //Get the next line of input if there is any; if not, sleep for a millisecond and return an empty string.
        string GetLine()
        {
            //See if the next line of input, if any, is ready to be processed.
            if (sendOverNextLine)
            {
                //Don't consume the CPU while waiting for input; this_thread::yield()
                //would still consume a lot of CPU, so sleep must be used.
                this_thread::sleep_for(chrono::milliseconds(1));

                return "";
            }
            else
            {
                //Retrieve the next line of input from the getline thread and store it for return.
                inputLock.lock();
                string returnInput = input;
                inputLock.unlock();

                //Also, signal to the getline thread that it can continue
                //sending over the next line of input, if available.
                sendOverNextLine = true;

                return returnInput;
            }
        }

    private:
        //Cross-thread-safe boolean to tell the getline thread to stop when AsyncGetline is deconstructed.
        atomic<bool> continueGettingInput;

        //Cross-thread-safe boolean to denote when the processing thread is ready for the next input line.
        //This exists to prevent any previous line(s) from being overwritten by new input lines without
        //using a queue by only processing further getline input when the processing thread is ready.
        atomic<bool> sendOverNextLine;

        //Mutex lock to ensure only one thread (processing vs. getline) is accessing the input string at a time.
        mutex inputLock;

        //string utilized safely by each thread due to the inputLock mutex.
        string input;
};

void main()
{
    AsyncGetline ag;
    string input;

    while (true)
    {
        //Asynchronously get the next line of input, if any. This function automagically
        //sleeps a millisecond if there is no getline input.
        input = ag.GetLine();

        //Check to see if there was any input.
        if (!input.empty())
        {
            //Print out the user's input to demonstrate it being processed.
            cout << "{" << input << "}\n";

            //Check for the exit condition.
            if (input == "exit")
            {
                break;
            }
        }

        //Print out a space character every so often to demonstrate asynchronicity.
        //cout << " ";
        //this_thread::sleep_for(chrono::milliseconds(100));
    }

    cout << "\n\n";
    system("pause");
}
Cyndi answered 16/2, 2017 at 3:20 Comment(5)
Hey, I put a lot of work into this, and it works nice and has very good comments. Why downvote?Cyndi
I'm not sure as to why this was downvoted, but here's an upvote!Unready
Small note: continueGettingInput, inputLock etc. will be garbage as soon as the destructor ends (AKA undefined behavior & a crash at best). You need to make it a shared_ptr and capture it by value in the polling thread (perhaps pack it in a SharedState struct).Coordinate
This is great, thank you Andrew. helmesjo, would naming the thread t and adding t.join() in the class destructor solve this without using smart pointers? std::atomic has doesn't allocate any memory, so I assume there would be no garbage then.Jennyjeno
I tested it on Linux and it works, however probably not the way it was meant to. In particular cin.peek() blocks and so this_thread.yield() is only called when cin is already closed.Melodymeloid
A
0

You can use cin.peek to check if there is anything to read, and then call getline if there is. There's no such thing as non-blocking getline by itself though.

Aldric answered 16/5, 2013 at 16:25 Comment(2)
That'll block, too, if there's no input avaliable.Deaconess
cin.peek should block because, according to standard: Returns: traits::eof() if good() is false. Otherwise, returns rdbuf()->sgetc() So as far as I understand it should not be possible to use peek() to check stream in unblocking way.Monzon
F
0

You can make a non-blocking equivalent to std::getline fairly easily using the istream::readsome() method. This reads available input up to a maximum buffer size, without blocking.

This function will always return instantly, but will capture a line if one is available on the stream. Partial lines are stored in a static variable until the next call.

bool getline_async(std::istream& is, std::string& str, char delim = '\n') {

    static std::string lineSoFar;
    char inChar;
    int charsRead = 0;
    bool lineRead = false;
    str = "";

    do {
        charsRead = is.readsome(&inChar, 1);
        if (charsRead == 1) {
            // if the delimiter is read then return the string so far
            if (inChar == delim) {
                str = lineSoFar;
                lineSoFar = "";
                lineRead = true;
            } else {  // otherwise add it to the string so far
                lineSoFar.append(1, inChar);
            }
        }
    } while (charsRead != 0 && !lineRead);

    return lineRead;
}
Floristic answered 5/9, 2019 at 16:50 Comment(4)
This doesn't actually appear to read anything (at least not when reading from console input). With above code I can't type in console.Coordinate
I'm working under Windows, and this also does not read anything for meNonappearance
Can you elaborate on how you're using this function, I've tested it successfully on windows and linux.Floristic
I don't think this function does what you think it does. It's non-blocking, so it will not wait for you write input. It will only return a line if one is already waiting in the stdin buffer.Floristic

© 2022 - 2024 — McMap. All rights reserved.