# Migrating from ASIO Deprecated Strand to Executor-Based Synchronization

> Migrate from ASIO deprecated strand to executor-based synchronization using asio bind executor for efficient handler execution. Learn how to simplify your Asio code.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: migration-guide
- Published: 2026-07-11

---

**Replace `asio::strand` with `asio::bind_executor` to serialize handler execution without maintaining a separate strand object, leveraging the `executor_binder` wrapper for lightweight, per-handler executor association.**

The chriskohlhoff/asio repository has transitioned from the legacy strand-based synchronization model to a lightweight executor-binding approach introduced in ASIO 1.38.0. This migration eliminates the need for dedicated `strand` objects that maintain internal shared state, replacing them with direct executor binding through `asio::bind_executor` as defined in [`include/asio/bind_executor.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/bind_executor.hpp).

## Understanding the Deprecated Strand Implementation

The original synchronization mechanism relied on `asio::strand<Executor>`, defined in [`include/asio/strand.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/strand.hpp), which guaranteed that handlers posted to the same strand never executed concurrently. Internally, the strand owned an implementation object of type `detail::strand_executor_service::implementation_type` (stored in the `impl_` member) that serialized calls to the underlying executor by intercepting `post`, `dispatch`, `defer`, and `execute` operations.

The convenience function `asio::make_strand()` and the `asio::io_context::strand` class (located in [`include/asio/io_context_strand.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/io_context_strand.hpp)) both carry deprecation warnings: `ASIO_DEPRECATED_MSG("Use asio::bind_executor()")`. These APIs required maintaining a separate strand object that acted as an intermediary between your executor and your handler, adding unnecessary overhead and complexity to asynchronous codebases.

## The Modern Executor-Based Approach

Modern ASIO code uses **executor binding** to associate executors directly with handlers or completion tokens. The `asio::bind_executor()` function returns an **`executor_binder<Handler, Executor>`** wrapper that stores the executor in its `executor_` member alongside the target callable. Unlike the deprecated strand, this wrapper forwards invocations directly to the target object without requiring additional shared state objects or synchronization primitives.

The generic async-result specializations defined in [`include/asio/bind_executor.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/bind_executor.hpp) automatically extract the executor when invoking handlers, allowing the binding to integrate seamlessly with ASIO's async initiation machinery. This design aligns with the broader execution-context model, enabling any executor to be bound on a per-handler basis rather than requiring a single strand type per execution context.

## Key Architectural Differences

**State Management**: The deprecated `asio::strand` maintained mutable shared state through its internal `impl_` object to track execution guarantees. The `executor_binder` approach is stateless regarding synchronization—it simply carries the executor as a property of the handler, relying on the underlying executor's guarantees.

**Flexibility**: Strands locked you into a single executor type per strand instance. With `asio::bind_executor`, you can bind different executors to different handlers within the same asynchronous operation chain, enabling mixed-executor pipelines.

**Integration**: Custom async result specializations for `asio::strand` were embedded directly in the strand class. The new model uses generic specializations in [`bind_executor.hpp`](https://github.com/chriskohlhoff/asio/blob/main/bind_executor.hpp) that work with any handler type without additional code.

## Migration Code Examples

### Converting Basic Timer Handlers

The old approach required creating a strand object and binding it to the handler:

```cpp
// Deprecated: Creating a strand from an io_context's executor
asio::io_context ctx;
asio::strand<asio::io_context::executor_type> s = asio::make_strand(ctx.get_executor());

void timer_handler(const asio::error_code& ec) {
    // ... code that must not run concurrently ...
}

asio::steady_timer t(ctx);
t.expires_after(std::chrono::seconds(1));
t.async_wait(
    asio::bind_executor(s, timer_handler)   // handler posted through strand
);

```

The modern approach binds the executor directly without intermediate strand objects:

```cpp
asio::io_context ctx;
auto exec = ctx.get_executor();  // any executor

// Bind executor directly to the callable
auto bound = asio::bind_executor(exec,
    [](const asio::error_code& ec) {
        // ... code that must not run concurrently ...
    });

asio::steady_timer t(ctx);
t.expires_after(std::chrono::seconds(1));
t.async_wait(bound);  // executor attached to token

```

### Using Partial Tokens for Multiple Handlers

You can create a partial token to apply the same executor binding across multiple asynchronous operations:

```cpp
asio::io_context ctx;
auto exec = ctx.get_executor();
auto token = asio::bind_executor(exec);  // partial token

t.async_wait(token([](auto&& ec){ /* handler 1 */ }));
sock.async_read_some(buf, token([](auto&& ec, std::size_t){ /* handler 2 */ }));

```

### Updating Coroutine-Based Code

When using `asio::spawn`, bind the executor directly to the coroutine handler:

```cpp
asio::io_context ctx;
auto exec = ctx.get_executor();

asio::spawn(
    ctx,
    asio::bind_executor(exec,
        [&](asio::yield_context yield) {
            asio::steady_timer timer(ctx);
            timer.expires_after(std::chrono::seconds(1));
            timer.async_wait(yield);  // runs on bound executor
        }),
    asio::detached);

```

## Summary

- The `asio::strand` class in [`include/asio/strand.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/strand.hpp) is deprecated in favor of direct executor binding via `asio::bind_executor` defined in [`include/asio/bind_executor.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/bind_executor.hpp).
- `asio::bind_executor` returns an `executor_binder` wrapper that stores the executor with the handler, eliminating the need for separate strand state management.
- This migration enables per-handler executor assignment, supporting mixed-executor pipelines that were impossible with the single-executor-per-strand limitation.
- The new approach reduces overhead by removing the indirection through `strand`'s internal `implementation_type` object while maintaining identical concurrency guarantees.
- All new ASIO code should use `asio::bind_executor()` instead of `asio::make_strand()` or `asio::io_context::strand`.

## Frequently Asked Questions

### What replaced `asio::strand` in modern ASIO code?

`asio::bind_executor` replaced the standalone strand pattern. Instead of creating a `asio::strand<Executor>` object and maintaining it throughout your codebase, you bind the executor directly to individual handlers using the function defined in [`include/asio/bind_executor.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/bind_executor.hpp). This returns an `executor_binder` that carries the executor as part of the handler itself.

### How does `asio::bind_executor` ensure handlers don't run concurrently?

The `executor_binder` wrapper does not itself provide serialization; rather, it ensures that the underlying executor—such as a strand-wrapped executor or a single-threaded context—is explicitly associated with the handler. When you bind a strand-wrapped executor (via `asio::make_strand`), the serialization guarantees come from the executor, not the binder. The binder simply ensures the correct executor is used for invocation.

### Can I use different executors for different handlers in the same operation?

Yes. Unlike the deprecated `asio::strand` which required a single executor per strand instance, `asio::bind_executor` allows you to bind different executors to different handlers on a per-call basis. This enables complex pipelines where different stages of an asynchronous operation execute on different thread pools or contexts.

### Where is the executor binding logic implemented in the ASIO source?

The core implementation resides in [`include/asio/bind_executor.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/bind_executor.hpp), which defines the `executor_binder` class template and the `bind_executor()` function overloads. Generic async result specializations in this file enable automatic executor extraction during handler invocation. The deprecated strand implementation remains in [`include/asio/strand.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/strand.hpp) and [`include/asio/io_context_strand.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/io_context_strand.hpp) for backward compatibility.