Awaiters & Async Primitives
Relevant source files
The following files were used as context for generating this wiki page:
Awaiters and async primitives in HXLibs provide high-level abstractions for managing concurrent C++20 coroutines. These tools allow you to synchronize multiple asynchronous tasks, handle race conditions, and aggregate results from disparate execution flows.
The system centers on two primary composition patterns: whenAny, which completes when the first task finishes, and whenAll, which completes when all tasks finish. These primitives integrate with the library's Task type and event loop system to enable non-blocking execution and result propagation.
Core Concurrency Primitives
HXLibs implements concurrency through specialized awaitable wrappers and operator overloading. These primitives manage the suspension and resumption of coroutines based on the state of child tasks.
Composition Operators
The library provides logical operator overloads to simplify task composition.
operator||: Combines tasks into awhenAnystructure. The first task to complete determines the overall result.operator&&: Combines tasks into awhenAllstructure. The aggregate completes only after every task finishes.
Sources: include/HXLibs/coroutine/awaiter/WhenAny.hpp:202-225, tests/coroutine/02_whenAll.cpp:39-43
The whenAny Primitive
whenAny executes multiple tasks concurrently and resumes the calling coroutine as soon as any single task completes. It returns a variant-like structure containing the result of the finished task and its index.
The diagram above shows the execution flow where the fastest task triggers the resumption of the parent, while subsequent task completions are handled by the internal control block.
Sources: include/HXLibs/coroutine/awaiter/WhenAny.hpp:41-105
The whenAll Primitive
whenAll synchronizes multiple tasks by waiting for every sub-task to reach a completed state. It returns a tuple or aggregate structure containing the results of all tasks.
Sources: include/HXLibs/coroutine/awaiter/WhenAll.hpp:1-50, tests/coroutine/02_whenAll.cpp:30-34
Component Architecture
Task Structure
The Task<T> struct is the fundamental unit of asynchronous work. It manages the lifetime of a coroutine handle and its associated promise.
| Component | Description |
|---|---|
promise_type | Manages the return value and suspension strategies. |
co_await operator | Returns an ExitAwaiter to handle coroutine suspension. |
runSync() | Resumes the handle immediately and returns the result if finished. |
destroy() | Cleans up the coroutine handle on task destruction. |
Sources: include/HXLibs/coroutine/task/Task.hpp:30-80
Internal Control Mechanisms
Both whenAny and whenAll utilize internal control blocks to track task progress and exceptions.
The WhenAnyCtlBlock coordinates the interaction between the sub-tasks and the waiting parent coroutine, ensuring exceptions are propagated and resumption happens exactly once.
Sources: include/HXLibs/coroutine/awaiter/WhenAny.hpp:44-85
Usage and Implementation
Result Handling
When using whenAny, the result is accessed via an index or a variant-like getter. For whenAll, structured binding is preferred for extracting individual task results.
// Example: whenAny result handling
auto res = co_await (task1() || task2());
log::hxLog.debug("Finished task index:", res.index());
// Example: whenAll result handling
auto t = co_await coroutine::whenAll(cTask01(), cTask02());
auto& [r1, r2] = t; // Extract results
Sources: tests/coroutine/01_whenAny.cpp:45-47, tests/coroutine/02_whenAll.cpp:32-34
Exception Propagation
If any sub-task throws an exception during a whenAny or whenAll operation, the internal control block captures the exception_ptr. The primitive rethrows this exception when the parent coroutine resumes, ensuring that error handling follows standard C++ coroutine semantics.
Sources: include/HXLibs/coroutine/awaiter/WhenAny.hpp:123-125, include/HXLibs/coroutine/awaiter/WhenAny.hpp:87-105
Summary
Awaiters and async primitives in HXLibs enable complex task orchestration through a declarative syntax. By using operator|| and operator&&, developers can express concurrent logic without manual handle management or complex state machines. These primitives ensure thread-safety and proper exception propagation within the library's event-driven architecture.