I/O Context
The io_context class is the heart of Corosio. It’s an event loop that
processes asynchronous I/O operations, manages timers, and coordinates
coroutine execution.
| Code snippets assume: |
#include <boost/corosio/io_context.hpp>
namespace corosio = boost::corosio;
Overview
Every Corosio program needs at least one io_context:
corosio::io_context ioc;
// ... create I/O objects and launch coroutines ...
ioc.run(); // Process events until all work completes
The io_context:
-
Owns the platform-specific I/O backend (IOCP on Windows)
-
Maintains a queue of pending work items
-
Provides an executor for coroutine dispatch
-
Tracks outstanding work to know when to stop
Construction
Default Construction
corosio::io_context ioc;
Creates an io_context with a concurrency hint equal to
std::thread::hardware_concurrency(). If more than one thread is available,
thread-safe synchronization is enabled.
With Concurrency Hint
corosio::io_context ioc(1); // Single-threaded, no synchronization
corosio::io_context ioc(4); // Up to 4 threads, thread-safe
The concurrency hint affects:
-
Internal synchronization strategy
-
IOCP thread pool size on Windows
Use 1 for single-threaded programs to avoid synchronization overhead.
Running the Event Loop
run()
Processes all pending work until stopped:
std::size_t n = ioc.run();
std::cout << "Processed " << n << " handlers\n";
This function:
-
Blocks until all work completes or
stop()is called -
Returns the number of handlers executed
-
Automatically stops when no outstanding work remains
run_one()
Processes at most one work item:
std::size_t n = ioc.run_one(); // Returns 0 or 1
Useful for manual event loop control or interleaving with other work.
Stopping and Restarting
The Executor
The io_context::executor_type provides the interface for dispatching work:
auto ex = ioc.get_executor();
// Launch a coroutine
capy::run_async(ex)(my_coroutine());
// Access the context
corosio::io_context& ctx = ex.context();
// Check if running inside the event loop
if (ex.running_in_this_thread())
std::cout << "Inside run()\n";
Executor Operations
auto ex = ioc.get_executor();
// Dispatch: symmetric transfer if inside run(), otherwise post
ex(handle);
// Post: always queue for later execution
ex.post(handle);
// Defer: same as post (conveys continuation intent)
ex.defer(handle);
The dispatch operation ex(handle) enables symmetric transfer when already
running inside run(). This is how child coroutines resume parents
efficiently.
Typical Usage Pattern
int main()
{
corosio::io_context ioc;
// Create I/O objects
corosio::socket sock(ioc);
corosio::timer timer(ioc);
// Launch initial coroutine
capy::run_async(ioc.get_executor())(main_coroutine(sock, timer));
// Run until all work completes
ioc.run();
}
Thread Safety
The io_context can be used from multiple threads when constructed with
a concurrency hint greater than 1:
corosio::io_context ioc(4);
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i)
threads.emplace_back([&ioc] { ioc.run(); });
for (auto& t : threads)
t.join();
Multiple threads can call run() concurrently. The io_context distributes
work across threads.
| Individual I/O objects (sockets, timers) are not thread-safe. Don’t access the same socket from multiple threads without synchronization. |
Inheritance from execution_context
io_context inherits from capy::execution_context, providing service
management:
// Create or get a service
my_service& svc = ioc.use_service<my_service>();
// Check if service exists
if (ioc.has_service<my_service>())
// ...
Services are destroyed when the io_context is destroyed.
Platform Details
Windows (IOCP)
On Windows, the io_context uses I/O Completion Ports:
-
Scalable to thousands of concurrent connections
-
Efficient thread pool utilization
-
Native async I/O with zero-copy potential
Next Steps
-
Sockets — I/O with TCP sockets
-
Acceptors — Accept incoming connections
-
Timers — Async delays and timeouts
-
Affine Awaitables — The dispatch protocol