# ASIO Coroutine Support Features: From Stackless Macros to C++20 Awaitables

> Explore ASIO's coroutine support including stackless macros, C++20 awaitables, and Boost Fibers. Learn about cancellation and executor propagation in this comprehensive guide.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: deep-dive
- Published: 2026-07-12

---

**ASIO provides three distinct coroutine models: stackless macros for pre‑C++20 compilers, native C++20 `awaitable` coroutines with `co_spawn`, and Boost.Fiber‑style spawn functions, all supporting cancellation and executor propagation.**

The `chriskohlhoff/asio` library ships with a comprehensive coroutine toolkit that spans legacy compilers lacking C++20 support all the way to modern asynchronous code using `co_await`. Understanding ASIO's coroutine support features helps you choose the right abstraction for your networking code, whether you need lightweight state machines or fully structured concurrency.

## Stackless Coroutines (Macro-Based)

ASIO implements **stackless coroutines** through a set of macros that transform a class into a state machine using a single `int` state and a `switch` statement. This model requires no compiler coroutine support and lives in [[`asio/coroutine.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/coroutine.hpp)](https://github.com/chriskohlhoff/asio/blob/master/include/asio/coroutine.hpp).

### How Stackless Coroutines Work

The mechanism relies on three pseudo‑keywords:

- **`reenter (this)`** – Defines the coroutine body and initializes the state machine.
- **`yield`** – Saves the current state, initiates an asynchronous operation, and suspends execution until completion.
- **`fork`** – Creates child coroutines (checkable via `is_child()` and `is_parent()`).

Classes inherit from `asio::coroutine` and implement `operator()` with an error code and bytes‑transferred parameter. The macro expansion generates a `switch` statement that jumps to the correct case label on re‑entry.

```cpp
#include <asio.hpp>
#include <iostream>

class echo_session : public asio::coroutine
{
public:
    echo_session(asio::ip::tcp::socket socket)
        : socket_(std::move(socket)) {}

    void operator()(asio::error_code ec = {}, std::size_t n = 0)
    {
        reenter (this)
        {
            for (;;)
            {
                yield socket_.async_read_some(
                    asio::buffer(data_), *this);
                
                yield asio::async_write(
                    socket_, asio::buffer(data_, n), *this);
            }
        }
    }

private:
    asio::ip::tcp::socket socket_;
    char data_[1024];
};

```

## C++20 Coroutine Support

Modern ASIO leverages the C++20 coroutine language features through the `awaitable` return type and associated helpers in [[`asio/awaitable.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/awaitable.hpp)](https://github.com/chriskohlhoff/asio/blob/master/include/asio/awaitable.hpp) and [[`asio/co_spawn.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/co_spawn.hpp)](https://github.com/chriskohlhoff/asio/blob/master/include/asio/co_spawn.hpp).

### The awaitable Return Type

`awaitable<T, Executor>` is a completion token that tells ASIO to use the `co_await` keyword. When you mark a function with `awaitable<std::size_t>`, the compiler generates a coroutine frame that suspends at `co_await` points and resumes via ASIO’s internal machinery.

Use `asio::use_awaitable` as the completion token to trigger this behavior:

```cpp
asio::awaitable<std::size_t> echo(asio::ip::tcp::socket socket)
{
    std::size_t total = 0;
    char data[1024];

    try
    {
        for (;;)
        {
            std::size_t n = co_await socket.async_read_some(
                asio::buffer(data), asio::use_awaitable);

            co_await asio::async_write(
                socket, asio::buffer(data, n), asio::use_awaitable);

            total += n;
        }
    }
    catch (std::exception const&) { }

    co_return total;
}

```

### Launching Coroutines with co_spawn

[`co_spawn`](https://github.com/chriskohlhoff/asio/blob/master/include/asio/co_spawn.hpp) bridges `awaitable` functions with ASIO’s executor model. It takes an executor, a callable returning `awaitable<T>`, and a completion token (or handler) to receive the final result or exception.

```cpp
asio::co_spawn(
    ctx,
    echo(std::move(sock)),
    [](std::exception_ptr e, std::size_t n)
    {
        if (!e) std::cout << "Transferred " << n << " bytes\n";
    });

```

## Boost.Fiber Integration with spawn

[[`asio/spawn.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/spawn.hpp)](https://github.com/chriskohlhoff/asio/blob/master/include/asio/spawn.hpp) provides `asio::spawn`, a higher‑level API that works with both the legacy stackless model (`yield_context`) and C++20 coroutines. Internally it uses Boost.Fiber or Boost.Context to manage stackful coroutines.

You can wrap a C++20 `awaitable` inside `spawn` for fire‑and‑forget scenarios:

```cpp
asio::spawn(
    ctx,
    [sock = std::move(sock)]() mutable -> asio::awaitable<void>
    {
        co_await async_echo(std::move(sock));
    },
    asio::detached);

```

The `detached` completion token discards the result, making this pattern ideal for server loops that accept connections indefinitely.

## Cancellation and Executor Access

Per‑coroutine metadata lives in [[`asio/this_coro.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/this_coro.hpp)](https://github.com/chriskohlhoff/asio/blob/master/include/asio/this_coro.hpp). This header provides the `this_coro` namespace for runtime introspection.

### Per-Coroutine Cancellation State

Call `asio::this_coro::cancellation_state()` to query the current cancellation status, or `asio::this_coro::reset_cancellation_state()` to clear it. Both macro‑based and C++20 coroutines respect these calls, allowing you to implement graceful shutdowns.

### Executor Propagation

`awaitable` carries an associated executor (defaulting to `any_io_executor`). Inside a coroutine, retrieve the current executor with:

```cpp
auto ex = co_await asio::this_coro::executor;

```

This ensures that nested operations run on the correct strand or thread pool without explicit executor passing.

## Summary

- **Stackless macros** (`reenter`, `yield`, `fork`) in [`asio/coroutine.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/coroutine.hpp) provide lightweight state machines for pre‑C++20 compilers.
- **C++20 coroutines** use `awaitable<T>` from [`asio/awaitable.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/awaitable.hpp) and are launched via `co_spawn` from [`asio/co_spawn.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/co_spawn.hpp).
- **`asio::spawn`** in [`asio/spawn.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/spawn.hpp) offers a Fiber‑compatible wrapper that bridges both worlds.
- **Cancellation and executors** are accessible via [`asio/this_coro.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/this_coro.hpp) using `cancellation_state()` and `executor`.
- All three models integrate seamlessly with ASIO’s executor and I/O object model.

## Frequently Asked Questions

### What is the difference between stackless and C++20 coroutines in ASIO?

Stackless coroutines are implemented as macros that generate a `switch` statement around a hidden state variable; they require inheritance from `asio::coroutine` and use `yield` for suspension. C++20 coroutines are compiler‑generated state machines using `co_await` and `co_return` with the `awaitable` return type, providing cleaner syntax and type safety.

### How do I handle cancellation in ASIO coroutines?

Include [`asio/this_coro.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/this_coro.hpp) and call `asio::this_coro::cancellation_state()` to check if the coroutine has been cancelled. You can also reset the state with `reset_cancellation_state()`. Both the macro‑based and C++20 APIs respect the cancellation slot associated with the coroutine’s executor.

### Can I mix stackless macros and C++20 awaitables in the same project?

Yes. ASIO’s design keeps these two models orthogonal. You can use stackless coroutines for legacy components while writing new code with `awaitable` and `co_spawn`, provided you compile the C++20 parts with a conforming compiler. The `asio::spawn` function can even wrap `awaitable` coroutines for compatibility with older code expecting `yield_context`.

### Which header should I include for basic C++20 coroutine support?

Include [`asio/awaitable.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/awaitable.hpp) for the return type and [`asio/co_spawn.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/co_spawn.hpp) to launch coroutines. If you need cancellation or executor access, also include [`asio/this_coro.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/this_coro.hpp). For the completion token, use `asio::use_awaitable` defined in [`asio.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio.hpp) or [`asio/use_awaitable.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/use_awaitable.hpp).