| Home | Libraries | People | FAQ | More

## Tutorial 2: going async with C++20 coroutines

In the previous tutorial we used synchronous functions. They are simple, but have a number of limitations:

- They aren't as versatile as async functions. For example, there is no way to set a timeout to a sync operation.
- They don't scale well. Since sync functions block the calling thread until they complete, you need to create OS threads to achieve parallelism. This doesn't scale well and leads to the inherent complexities of multi-threaded programs.
- Some classes (like `connection_pool`) only offer an async interface.

For this reason, we recommend to always use async functions. All Asio-compatible libraries (including this one) allow async programming using a variety of styles. In this chapter, we will explain how to use C++20 coroutines because they are the simplest to use.

| Note
Still not using C++20? Don't worry, you can use stackful coroutines and callbacks even in C++11.

#### What is a coroutine?

Roughly speaking, it's a function that can suspend and resume, keeping local variables alive in the process. Suspension happens when reaching a `co_await` expression. These usually appear when the program performs an I/O operation. When an expression like this is encountered, the following happens:

1. The coroutine initiates the I/O operation.
2. The coroutine suspends, passing control back to the `io_context` (that is, the event loop).
3. While the I/O operation is in progress, the `io_context` may run other operations, like other coroutines.
4. When the I/O operation completes, the `io_context` resumes the coroutine immediately after the `co_await` expression.

#### Transforming sync code into coroutines

Recall the following code from our previous tutorial:

    // The hostname, username and password to use
    mysql::connect_params params;
    params.server_address.emplace_host_and_port(hostname);
    params.username = username;
    params.password = password;

    // Connect to the server
    conn.connect(params);

    // Issue the SQL query to the server
    const char* sql = "SELECT 'Hello world!'";
    mysql::results result;
    conn.execute(sql, result);

    // Print the first field in the first row
    std::cout << result.rows().at(0).at(0) << std::endl;

    // Close the connection
    conn.close();

To transform this code into a coroutine, we need to:

- Extract it to a separate function returning `boost::asio::awaitable<void>`.
- Replace sync functions (like `any_connection::connect`) by async ones (like `any_connection::async_connect`).
- Place a `co_await` operator in front of each I/O operation.

Doing this, we have:

    asio::awaitable<void> coro_main(
        mysql::any_connection& conn,
        std::string_view server_hostname,
        std::string_view username,
        std::string_view password
    )
    {
        // The hostname, username, password and database to use.
        // TLS is used by default.
        mysql::connect_params params;
        params.server_address.emplace_host_and_port(std::string(server_hostname));
        params.username = username;
        params.password = password;

        // Connect to the server
        co_await conn.async_connect(params);

        // Issue the SQL query to the server
        const char* sql = "SELECT 'Hello world!'";
        mysql::results result;
        co_await conn.async_execute(sql, result);

        // Print the first field in the first row
        std::cout << result.rows().at(0).at(0) << std::endl;

        // Close the connection
        co_await conn.async_close();
    }

Note that the coroutine doesn't create or return explicitly any `boost::asio::awaitable<void>` object - this is handled by the compiler. The return type actually marks the function as being a coroutine. `void` here means that the coroutine doesn't return anything.

If any of the above I/O operations fail, an exception is thrown. You can prevent this by using `asio::redirect_error`.

#### Running our coroutine

As in the previous tutorial, we first need to create an `io_context` and a connection:

    // The execution context, required to run I/O operations.
    asio::io_context ctx;

    // Represents a connection to the MySQL server.
    mysql::any_connection conn(ctx);

To run a coroutine, use `asio::co_spawn`:

    // Enqueue the coroutine for execution.
    // This does not wait for the coroutine to finish.
    asio::co_spawn(
        // The execution context where the coroutine will run
        ctx,

        // The coroutine to run. This must be a function taking no arguments
        // and returning an asio::awaitable<T>
        [&conn, argv] { return coro_main(conn, argv[3], argv[1], argv[2]); },

        // Callback to run when the coroutine completes.
        // If any exception is thrown in the coroutine body, propagate it to terminate the program.
        [](std::exception_ptr ptr) {
            if (ptr)
            {
                std::rethrow_exception(ptr);
            }
        }
    );

Note that this will only schedule the coroutine. To actually run it, we need to call `io_context::run`. This will block the calling thread until all the scheduled coroutines and I/O operations complete:

    // Calling run will actually execute the coroutine until completion
    ctx.run();

#### Next steps

Full program listing for this tutorial is here.

You can now proceed to the next tutorial.