non-blocking std::getline, exit if no input

岁酱吖の 提交于 2019-12-19 05:31:19

问题


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).


回答1:


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:

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");
}



回答2:


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.




回答3:


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;
}


来源:https://stackoverflow.com/questions/16592357/non-blocking-stdgetline-exit-if-no-input

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!