Which io_context does stdboostasiopost / dispatch use?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When using Boost.Asio for asynchronous programming in C++, boost::asio::post and boost::asio::dispatch are the primary functions for scheduling work onto an executor. A common question is: which io_context do these functions use? The answer depends on how you call them and what executor is associated with the handler. This article breaks down the rules governing io_context selection for both post and dispatch, with code examples that demonstrate each scenario.
What Is io_context?
The io_context (called io_service in older versions of Boost.Asio) is the core event loop in Boost.Asio. It manages a queue of pending handlers and executes them when you call io_context::run(). Every asynchronous operation in Boost.Asio ultimately delivers its completion handler through an io_context.
In this example, post explicitly receives io as the first argument, so there is no ambiguity. The handler runs on that specific io_context.
How post Selects the io_context
boost::asio::post has two overloads that determine which io_context is used.
Overload 1: Explicit Executor or io_context
When you pass an executor (or io_context) as the first argument, the handler is queued on that executor. This is the most straightforward case.
Each handler runs on the io_context that was explicitly provided.
Overload 2: Handler with Associated Executor
When you call post with only a handler (no explicit executor), Boost.Asio inspects the handler for an associated executor. The associated executor is determined through the boost::asio::associated_executor trait. If the handler was bound to an executor using boost::asio::bind_executor, that bound executor is used.
Here, even though post receives no explicit io_context, the handler carries its executor association, and post uses that.
If the handler has no associated executor and you call post without an explicit executor, Boost.Asio uses the system executor (boost::asio::system_executor), which runs the handler on a system-managed thread.
How dispatch Differs from post
The key difference between dispatch and post is that dispatch may execute the handler immediately if certain conditions are met, while post always defers execution.
post always enqueues the handler for later execution. It never runs the handler inline. This guarantees that the handler executes outside the calling context.
dispatch checks whether the caller is already running inside the target io_context. If so, it executes the handler immediately (inline). If not, it behaves like post and enqueues the handler.
Output:
The dispatch call executes its handler immediately because the code is already running inside io.run(). The post call enqueues its handler, so it runs after the current handler finishes.
When to Use Each
Use post when you want to guarantee that the handler runs asynchronously, outside the current call stack. This is important for avoiding reentrancy issues. For example, if a handler modifies shared state and another handler reads it, post ensures they do not run simultaneously on the same thread.
Use dispatch when you want the handler to run as soon as possible. If you are already inside the right execution context, dispatch avoids the overhead of queuing and dequeuing. This is useful for performance-sensitive paths where the handler is lightweight.
Strand-Based Execution
When using boost::asio::strand for serialized execution, post and dispatch respect the strand's ordering guarantees. A strand ensures that handlers posted to it never execute concurrently, even across multiple threads.
The strand guarantees that "First" prints before "Second," regardless of how many threads call io.run().
Common Pitfalls
- Assuming dispatch is always deferred. Unlike
post,dispatchcan run the handler synchronously. If your handler modifies data that the caller is also using, this inline execution can cause subtle bugs. Usepostwhen you need guaranteed deferral. - Forgetting to call io_context::run(). Handlers queued with
postordispatchwill not execute until some thread callsrun()on the associatedio_context. Ifrun()is not called, the program appears to hang or silently drops work. - Mixing io_context instances unintentionally. If your application has multiple
io_contextobjects, make sure each handler is posted to the correct one. A handler posted toio1will never execute if onlyio2.run()is called. - Not using strands for shared state. Without a strand, handlers on the same
io_contextcan run concurrently across multiple threads. If those handlers access shared data, you need either a strand or explicit locking.
Summary
boost::asio::post and dispatch use the io_context you provide explicitly as the first argument. If no executor is provided, they fall back to the handler's associated executor (set via bind_executor) or the system executor. The key difference is that post always defers execution, while dispatch runs the handler inline if the caller is already inside the target executor's context. Use post for safety and dispatch for performance when reentrancy is not a concern.

