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

后端 未结 3 1013
遇见更好的自我
遇见更好的自我 2021-01-11 11:51

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 i

相关标签:
3条回答
  • 2021-01-11 12:26

    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.

    0 讨论(0)
  • 2021-01-11 12:33

    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");
    }
    
    0 讨论(0)
  • 2021-01-11 12:34

    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;
    }
    
    0 讨论(0)
提交回复
热议问题