# How ASIO Handles Asynchronous Operations: Completion Tokens and io_context Explained

> Learn how ASIO handles asynchronous operations using completion tokens and io_context. Understand ASIO's core mechanisms for efficient non-blocking I/O.

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

---

**ASIO handles asynchronous operations through a combination of completion tokens, the `async_initiate` helper, and an `io_context` that drives the execution of completion handlers.**

The chriskohlhoff/asio repository provides a cross-platform C++ library for network and low-level I/O programming. Understanding how ASIO handles asynchronous operations requires examining its type-safe token-based design and the event loop architecture that schedules work across threads.

## Completion Tokens and the async_result Framework

At the heart of ASIO's asynchronous model lies the **completion token** concept. Users supply tokens—such as `asio::awaitable<void>`, `std::function<void(error_code)>`, or custom Boost-compatible tokens—that define how operation results are delivered.

The generic `async_result` machinery in [`include/asio/async_result.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/async_result.hpp) examines the token type and adapts it to the required handler signature. This template-based approach decouples the asynchronous operation implementation from the result delivery mechanism, enabling support for callbacks, futures, and coroutines through a unified interface.

## The async_initiate Pattern

Every asynchronous function forwards its work to `asio::async_initiate<CompletionToken, Signature>(init, args...)`. This templated helper creates a completion handler matching the token's signature and then invokes the user-provided *init* object to start the OS-level operation.

This pattern appears consistently throughout the library. In [`include/asio/write.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/write.hpp), the `async_write` operations use `async_initiate` to bridge between the high-level API and platform-specific implementations. Similarly, [`include/asio/spawn.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/spawn.hpp) demonstrates how stackful coroutines integrate with the same initiation mechanism, converting the completion token into a suspension point for the coroutine.

## The io_context Event Loop

The **io_context** class in [`include/asio/io_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/io_context.hpp) serves as the core event loop, owning a scheduler (or IOCP on Windows) that stores pending operations and dispatches their handlers. The class provides several execution methods:

- `run()` – Blocks until all work finishes and executes handlers
- `run_one()` – Executes at most one ready handler  
- `poll()` – Executes ready handlers without blocking

Work submission occurs through free functions such as `asio::post`, `asio::dispatch`, and `asio::defer`, which enqueue callables into the `io_context`. The [`defer.hpp`](https://github.com/chriskohlhoff/asio/blob/main/defer.hpp) header implements the lazy task submission semantics, allowing handlers to be scheduled without immediate execution.

## From OS Completion to Handler Invocation

When an OS operation completes—such as when a socket becomes readable—the associated OS-specific callback posts a handler to the `io_context`. During the next `run()` iteration, the scheduler invokes this handler, fulfilling the completion token. If the token represents a C++20 coroutine (`awaitable`), the coroutine resumes; if it is a traditional callback, the function executes directly.

This flow creates a consistent pipeline: user code calls an async function, which uses `async_initiate` to start the OS operation, and the `io_context` eventually invokes the completion handler when the operation finishes.

## Practical Implementation Examples

The following examples demonstrate different completion token types with the same underlying asynchronous infrastructure.

Using a callback token:

```cpp
void on_read(const asio::error_code& ec, std::size_t bytes) {
  if (!ec) std::cout << "Read " << bytes << " bytes\n";
}

asio::ip::tcp::socket sock(io);
sock.async_read_some(asio::buffer(data), on_read);
io.run();

```

Using C++20 coroutines with awaitable tokens:

```cpp
asio::awaitable<void> echo(asio::ip::tcp::socket sock) {
  char data[1024];
  for (;;) {
    std::size_t n = co_await sock.async_read_some(
        asio::buffer(data), asio::use_awaitable);
    co_await asio::async_write(sock,
        asio::buffer(data, n), asio::use_awaitable);
  }
}

asio::co_spawn(io, echo(std::move(sock)), asio::detached);
io.run();

```

Posting custom tasks to the io_context:

```cpp
void my_task() {
  std::cout << "Task executed inside io_context\n";
}

asio::post(io, my_task);
io.run();

```

## Summary

- **Completion tokens** decouple result delivery from operation implementation, defined in [`include/asio/async_result.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/async_result.hpp).
- **async_initiate** creates handlers and initiates OS operations, appearing in [`include/asio/write.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/write.hpp) and [`include/asio/spawn.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/spawn.hpp).
- **io_context** manages the event loop and scheduler, implemented in [`include/asio/io_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/io_context.hpp).
- Handler submission via `asio::post`, `asio::dispatch`, and `asio::defer` integrates with the scheduler.
- OS completion callbacks trigger handler invocation through the `io_context`, supporting both traditional callbacks and C++20 coroutines.

## Frequently Asked Questions

### What is a completion token in ASIO?

A completion token is a user-provided object that determines how the result of an asynchronous operation is delivered. The token can be a function pointer, a lambda, a `std::future`, or a coroutine awaitable. The `async_result` template in [`include/asio/async_result.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/async_result.hpp) adapts these tokens to the internal handler signature required by the library.

### How does async_initiate work?

`async_initiate` is a templated helper function that creates a completion handler matching the token's signature and then calls the provided initiation object to start the OS-level operation. This pattern standardizes how async functions begin work while remaining agnostic to the completion token type, as seen in networking operations like those in [`include/asio/write.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/write.hpp).

### What is the difference between asio::post and asio::dispatch?

`asio::post` unconditionally enqueues a handler for later execution by the `io_context`, even if called from within the context's thread. `asio::dispatch` executes the handler immediately if called from a thread currently running the `io_context`, otherwise it behaves like `post`. Both functions manage work submission to the event loop defined in [`include/asio/io_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/io_context.hpp).

### How does ASIO support C++20 coroutines?

ASIO supports C++20 coroutines through the `asio::awaitable` token type defined in [`include/asio/awaitable.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/awaitable.hpp). When used with `co_await`, operations suspend the coroutine and resume it when the completion handler fires. The `asio::co_spawn` function launches coroutines as tasks within the `io_context`, while [`include/asio/spawn.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/spawn.hpp) provides similar support for stackful coroutines.