c++boostboost-asioasio

Unexpected Behavior of Boost.Asio's io_context without Running ioc.run() - Why Does it Work?


I try to understand how iocontext works and created a simple program to understand its better step by step. As you can see, I even not to run ioc.run(). And I believed this code would do nothing. But when I run it and type something like

aaa;bbb;ccc;ddd

then get something like

ThreadID = ThreadID = 17364; bbbThreadID = 72872; ccc
ThreadID = 5428025840; aaa
; ddd

And under debug I see a lot of (about 40) threads.

What does it mean? Is it the right behaviour? And why does it work at all?

#include <iostream>

#define _WIN32_WINNT 0x0A00
#include <boost/asio.hpp>

#include <sstream>

std::vector<std::string> parseInput(const std::string& input)
{
    std::vector<std::string> result;
    std::istringstream iss(input);
    std::string token;

    while (std::getline(iss, token, ';'))
    {
        result.push_back(token);
    }

    return result;
}


int main()
{
    try
    {
        boost::asio::io_context ioc;

        for (;;)
        {
            std::string input;
            std::getline(std::cin, input);
            std::vector<std::string> tokens = parseInput(input);

            if (tokens.empty())
                break;

            for (const std::string& token : tokens)
            {
                boost::asio::post([token]()
                    {
                        std::cout << "ThreadID = " << std::this_thread::get_id() << "; " << token << std::endl;
                    });
            }
        }

    }
    catch (std::exception& exc)
    {
        std::cerr << exc.what() << std::endl;
    }

    return 0;
}

Win12, boost::asio 1.82.0, MSVC 2022 (I used different compilers, c++ 14 and c++ 20)


Solution

  • You're posting to the system executor, which is usually implemented as a global thread pool.

    Where does it come from?

    You use the overload of asio::post that doesn't specify an explicit executor. Therefore, the associated executor will be used. No executor has been associated with the posted handler. get_associated_executor will return the default executor, which, if unspecified, will itself default to asio::system_executor{}.

    Solve it

    1. Using an explicit executor:

      asio::post(ioc.get_executor(), handler);
      

      ADL makes it so that you can probably use unqualified:

      post(ioc.get_executor(), handler);
      

      Next up, as a convenience Asio supports posting “to” an execution context, in which case its default executor will be used:

      post(io_context, handler);
      
    2. binding an executor, e.g. using bind_executor:

      asio::post(bind_executor(ioc, handler));
      

    E.g. here's my reviewed listing:

    live On Coliru

    // #define _WIN32_WINNT 0x0A00
    #include <iostream>
    #include <boost/asio.hpp>
    #include <sstream>
    namespace asio = boost::asio;
    
    inline static std::atomic_int tidgen    = 0;
    thread_local static int const thread_id = tidgen++;
    
    static std::vector<std::string> parseLine(std::string input) {
        std::vector<std::string> result;
        std::istringstream       iss(std::move(input));
    
        for (std::string token; getline(iss, token, ';');)
            result.push_back(token);
    
        return result;
    }
    
    int main() try {
        asio::io_context ioc;
    
        for (std::string line; getline(std::cin, line);) {
            for (std::string& token : parseLine(std::move(line))) {
                auto handler = [t = std::move(token)] {
                    std::cout << "ThreadID = " << thread_id << "; " << t << std::endl;
                };
                asio::post(ioc, handler);
            }
        }
    
        ioc.run();
    } catch (std::exception const& exc) {
        std::cerr << exc.what() << std::endl;
    }
    

    Compare with the version that accidentally uses system_executor