Async C++ with boost::asio

by Tyler Calabrese

April - 4 - 2024

Presentation created from Markdown using marp


Part 1: Intro


Preface: A little bit about Boost

Welcome to Boost.org! Boost provides free peer-reviewed portable C++ source libraries. (boost)

The Boost community emerged around 1998, when the first version of the standard was released. It has grown continuously since then and now plays a big role in the standardization of C++. (wikipedia)

Many modern C++ features that developers now take for granted, such as smart pointers, tuples, and even std::thread, began as Boost library features. (wikipedia)


As a full-time C++ developer, here are, in my professional opinion, the best ways to write an async program in C++:


As a full-time C++ developer, here are, in my professional opinion, the best ways to write an async program in C++:

  1. don't

As a full-time C++ developer, here are, in my professional opinion, the best ways to write an async program in C++:

  1. don't
  2. boost::asio
  3. std::thread
  4. New features in C++20

Ok, then, why:


Example Use Cases

  1. Get performance benefits by running some tasks in parallel

Example Use Cases

2. Repeating tasks

Example Use Cases

Notice what these all have in common:

Whether writing to a file or communicating over a network, these are all I/O operations.

Hence, boost async io. asio!


Part 2: Fundamentals


The I/O Context


The I/O Context

This I/O execution context represents your program’s link to the operating system’s I/O services. (boost)


Example: Post a single operation to an I/O context

int main()
{
    boost::asio::io_context ctx;
    boost::asio::post(ctx, [](){ std::cerr << "hi!\n"; });
    ctx.run();
}

download this code


Strands


Strands


Completion Tokens


Completion Tokens

the I/O execution context dequeues the result, translates it into an error_code, and then passes it to your completion handler. (boost)


Part 3: asio in Action


Example: Repeat Timer

class RepeatTimer
{
public:
    explicit RepeatTimer(boost::asio::io_context& ctx, std::chrono::milliseconds every, std::function<void()> task)
    : m_timer(boost::asio::make_strand(ctx), every), m_every(every), m_task(task)
    {
        m_timer.async_wait([this](boost::system::error_code ec){ doRunOne(ec); });
    }
    ~RepeatTimer() = default; // destroying the timer cancels it, too
    
    void cancel() { m_timer.cancel(); }
    
private:
    void doRunOne(boost::system::error_code ec);
    
    boost::asio::steady_timer m_timer;
    std::chrono::milliseconds m_every;
    std::function<void()> m_task;
};

void RepeatTimer::doRunOne(boost::system::error_code ec)
{
    // we canceled or destroyed the timer
    if (ec == boost::asio::error::operation_aborted)
        return;
     // there was a problem
    else if (ec)
        throw std::runtime_error{"Repeat timer got error code " + std::to_string(ec.value())};

    // normal operation. Run our task and repeat
    m_timer.expires_after(m_every);
    m_task();
    m_timer.async_wait([this](boost::system::error_code ec){ doRunOne(ec); });
}

Example: Repeat Timer

int main()
{
    int counter = 0;
    boost::asio::io_context ctx;
    RepeatTimer t{ctx, std::chrono::seconds{1}, [&](){
        std::cerr << counter++ << std::endl;
    }};
    ctx.run_for(std::chrono::seconds{4});
}

download this code


Running an I/O Context from Multiple Threads

int main()
{
    constexpr int c_num_threads = 3;
    boost::asio::io_context ctx;

    for (int i = 0; i < c_num_threads; i++)
    {
        boost::asio::post(ctx, [](){
            std::this_thread::sleep_for(std::chrono::seconds{1});
            std::cerr << "hi!\n";
        });
    }

    boost::asio::thread_pool th{c_num_threads};
    for (int i = 0; i < c_num_threads; i++)
        boost::asio::post(th, [&ctx](){ ctx.run(); });

    th.join();

    return EXIT_SUCCESS;
}

Exercise: Let’s asio-ify this code together


Exercise: Let’s asio-ify this code together

(my solution)


Q&A