Initiating Functions
Understand the functions that start asynchronous operations.
What is an Initiating Function?
An initiating function is a function that starts an asynchronous operation. When you call it, the operation begins—but it doesn’t necessarily complete before the function returns.
Examples:
-
socket.async_connect(endpoint, token) -
socket.async_read_some(buffer, token) -
timer.async_wait(token) -
async_read(stream, buffer, token)
Anatomy of an Initiating Function
socket.async_read_some(buffer, completion_token);
// ~~~~~~~~~~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~
// Initiating func Args Completion token
Initiating function: The function that starts the operation.
Arguments: Operation-specific parameters (endpoint, buffer, etc.).
Completion token: Determines how the result is delivered.
With use_awaitable, the token makes the function return an awaitable:
awaitable<std::size_t> a = socket.async_read_some(buffer, use_awaitable);
std::size_t n = co_await a;
What Happens When You Call an Initiating Function?
-
The operation is initiated — The OS is asked to perform the work
-
The function returns — Control returns to the caller immediately
-
Time passes — The OS does the work in the background
-
The operation completes — The OS signals completion
-
The completion handler is invoked — Your code receives the result
With coroutines, steps 3-5 happen behind the co_await.
Immediate vs Deferred Completion
Some operations may complete immediately. For example, reading from a socket that already has data buffered. Asio handles this correctly—the operation still goes through the event loop to invoke the completion handler.
// Even if data is ready, the coroutine still suspends
// and resumes via the event loop
std::size_t n = co_await socket.async_read_some(buffer, use_awaitable);
This consistency is important: you can rely on the completion handler always being invoked asynchronously (not inline from the initiating function).
Return Values
Initiating functions with use_awaitable return awaitable<T> where T is
the result type:
| Operation | Return Type |
|---|---|
|
|
|
|
|
|
|
|
With as_tuple, error codes are included:
auto [ec, n] = co_await socket.async_read_some(
buffer,
asio::experimental::as_tuple(use_awaitable));
// Returns awaitable<std::tuple<error_code, std::size_t>>
Completion Signatures
Each initiating function documents its completion signature—the parameters passed to the completion handler. For example:
// async_read_some completion signature:
// void(error_code ec, std::size_t bytes_transferred)
// async_connect completion signature:
// void(error_code ec)
// async_accept completion signature:
// void(error_code ec, tcp::socket socket)
With use_awaitable:
-
void(error_code)→awaitable<void>(throws on error) -
void(error_code, T)→awaitable<T>(throws on error)
Cancellation
Initiating functions may be cancelled:
-
Close the I/O object (e.g.,
socket.close()) -
Call
cancel()(e.g.,timer.cancel()) -
Reset a timer’s expiry time
Cancelled operations complete with asio::error::operation_aborted:
try
{
co_await timer.async_wait(use_awaitable);
}
catch (const boost::system::system_error& e)
{
if (e.code() == asio::error::operation_aborted)
std::cout << "Timer was cancelled\n";
}
Overlapping Operations
On the same object, you can typically have:
-
Multiple reads outstanding? Usually no
-
Multiple writes outstanding? Usually no
-
A read and a write? Yes, these can overlap
// WRONG: Two reads on same socket
co_await socket.async_read_some(buf1, use_awaitable); // Started
co_await socket.async_read_some(buf2, use_awaitable); // Don't do this!
// OK: Read and write can overlap
auto read_task = socket.async_read_some(buf1, use_awaitable);
auto write_task = async_write(socket, buf2, use_awaitable);
co_await (read_task && write_task); // Both in parallel
The Completion Token Mechanism
The final parameter to an initiating function is the completion token. It determines how results are delivered:
| Token | Behavior |
|---|---|
|
Return |
|
Discard the result, don’t wait |
|
Include error code in result tuple |
|
Store error in |
This is one of Asio’s most powerful features: the same operation works with different async styles without changing the operation itself.
Next Steps
-
Completion Handlers — How completions are delivered
-
Memory and Lifetimes — Resource management guarantees