New Features: Async Control Structures
This release adds a suite of async control structures. They are documented in the new Control Structures section of the documentation site.
TMC async type | equivalent blocking type |
---|---|
tmc::mutex | std::mutex |
tmc::semaphore | std::counting_semaphore |
tmc::atomic_condvar<T> | std::atomic<T>::wait() |
tmc::barrier | std::barrier |
tmc::latch | std::latch |
tmc::manual_reset_event | Windows ManualResetEvent |
tmc::auto_reset_event | Windows AutoResetEvent |
Fix: TMC_WORK_ITEM=FUNC
std::function<void()>
is now fully supported as the underlying TMC work item type by setting the preprocessor definition TMC_WORK_ITEM=FUNC
. The only caveat is that you must also define TMC_TRIVIAL_TASK
, as std::function requires its held types to be copyable.
Fix: Always Track Priority
Previously there were some conditions in which a task could end up with an incorrect priority value after transitioning to and from an executor, queue, or event loop that didn't implement priority (such as ex_braid or ex_asio). This has been resolved, and tasks will now always track their priority correctly. This means that even if a task is running on an executor that doesn't actually implement priority:
tmc::current_priority()
will always report the correct assigned priority of the task- newly created child tasks will always inherit the correct assigned priority of their parents
- when exiting the non-priority executor back to a priority executor, they will be assigned to the correct priority queue
Also implemented in tmc-asio v0.0.11.
As part of this implementation, the number of possible priority levels is now limited to 16. If a task is submitted with priority higher than the maximum value for its executor, its priority will be clamped to the appropriate maximum value.
Enhancement: Awaitable Value Category Propagation
The value category (lvalue or rvalue) of an awaitable is now propagated through to the co_await or async_initiate expression, even if that awaitable is wrapped with spawn()
or spawn_tuple()
.
The rvalue_only_awaitable
type already existed to indicate awaitables that should be awaited exactly once, and are consumed afterward. The majority of TMC awaitables fall into this category - and their operator co_await()
is decorated with &&
to enforce it.
A new lvalue_only_awaitable
type has been created to indicate the opposite - awaitables that should be awaited multiple times. Currently this is used for the .result_each()
awaitable customizer, which produces a sequence of values. These types have their operator co_await()
decorated with &
to enforce that they are lvalues.
The combination of the above changes is that many invalid behaviors will now simply fail to compile. For example, awaiting an rvalue awaitable twice would cause an error at runtime. Awaiting an lvalue awaitable temporary would also cause an error at runtime. Now that the compiler can detect and block these errors, you can get feedback sooner and avoid painful debugging sessions.
spawn_many()
was not touched as part of this - it still always moves-from its iterator. Resolving this will be more difficult as it involves deducing the value category of the pointed-to object (if the iterator is a pointer).
Valid rvalue awaitable usage:
// temporary rvalue
co_await expr();
// explicit rvalue cast required
auto t = expr();
co_await std::move(t);
// wrappers have the same rules - temporary rvalue
co_await spawn_tuple(expr());
// explicit rvalue cast required
auto t = expr();
co_await tmc::spawn_tuple(std::move(t));
Valid lvalue awaitable usage:
// temporary rvalue not allowed, an lvalue must be created
auto t = expr();
co_await t;
co_await t;
// wrappers have the same rules
auto t = expr();
co_await tmc::spawn_tuple(t);
co_await tmc::spawn_tuple(t);
Enhancement: ex_cpu performance
Some improvements were made to the core worker loop and queue functions which reduce code footprint and improve instruction cache locality. This results in a ~1-3% performance improvement on synthetic benchmarks.
Breaking Changes
The rvalue enforcement rules will break code that depended on the previously incorrect behavior that spawn_tuple
would accept lvalues (of rvalue awaitables). Now that this behavior has been fixed, you can resolve any compilation issues by applying std::move() to your parameters at the call site.