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

49 views Asked by At

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)

1

There are 1 answers

0
sehe On BEST ANSWER

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