Aria 3.2.6
C++23 MVVM framework — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
Async & Coroutines

Aria's async layer requires C++23 and provides coroutine-based primitives for asynchronous work, built on top of the reactive graph. The key types:

  • Task<T> — lazy, single-shot coroutine awaitable
  • AsyncCommand<R, Args...> — three-state async action (executing / error / result)
  • CoroutineScope / ViewModelScope — structured concurrency tied to a lifetime
  • CancellationToken — cooperative cancellation
  • Combinators — when_all, when_any, with_timeout

Include: #include "aria/async/task.hpp", #include "aria/async/async_command.hpp", etc.


Task<T>

Task<T> is a lazy coroutine — it does nothing until co_awaited.

Basic Usage

aria::async::Task<int> compute_value() {
co_return 42;
}
aria::async::Task<void> show_result() {
int val = co_await compute_value();
std::cout << "Got: " << val << "\n";
}
Definition task.hpp:79

Void Specialization

aria::async::Task<void> log_message(std::string msg) {
std::cout << msg << "\n";
co_return;
}

Exception Handling

Exceptions thrown inside the coroutine body are stored and re-thrown at the co_await site:

throw std::runtime_error("boom");
co_return 0; // unreachable
}
try {
int v = co_await risky();
} catch (const std::runtime_error& e) {
// Caught here
}
}

Fire-and-Forget (Detached)

aria::async::Task<void> background_work() {
// Long-running work...
co_return;
}
// Start without awaiting — runs independently
background_work().start_detached();
void start_detached() &&
Start the task and detach it: the coroutine frame stays alive until the coroutine completes,...
Definition task.hpp:158

Pass a fresh task to start_detached(). An already-completed task is released without being resumed; a task still suspended in an asynchronous operation must remain under that operation's resumption control.

Warning: Detached tasks have no lifetime guard. Ensure captured references outlive the coroutine.


CancellationToken / CancellationSource

Cooperative cancellation. A CancellationSource owns the flag; a CancellationToken is a read-only view.

Destroying a source cancels its outstanding tokens and wakes cancellation waiters. Move assignment does the same for the destination's previous state before taking ownership of the incoming state; self-move is a no-op.

tok.is_cancelled(); // false
src.cancel();
tok.is_cancelled(); // true
Definition cancellation.hpp:221
void cancel()
Trigger cancellation.
Definition cancellation.hpp:242
CancellationToken token() const noexcept
Definition cancellation.hpp:225
Definition cancellation.hpp:182
bool is_cancelled() const noexcept
Definition cancellation.hpp:188

Inside a Coroutine

while (!tok.is_cancelled()) {
co_await aria::async::sleep_for(100ms);
tok.throw_if_cancelled(); // throws OperationCancelled
do_work();
}
}
void throw_if_cancelled() const
Throw OperationCancelled if cancelled. Call at safe await points.
Definition cancellation.hpp:193

Executor

Executors abstract scheduling — where coroutines run.

// Typically provided by the platform adapter
aria::async::IExecutor& ui; // main/UI thread
aria::async::IExecutor& worker; // background thread pool
Abstract executor interface — schedules a callable to run "somewhere".
Definition executor.hpp:37

Schedule On

Hop between executors:

aria::async::Task<UserProfile> load_profile(int uid) {
co_await aria::async::schedule_on(worker); // jump to background
auto data = fetch_from_db(uid); // blocking OK here
co_await aria::async::schedule_on(ui); // hop back to UI
co_return data;
}
auto schedule_on(IExecutor &exec)
Schedule a coroutine to resume on the given executor.
Definition executor.hpp:472

AsyncCommand<R, Args...>

An async command exposes three reactive properties that the UI can bind to:

Property Type Meaning
is_executing Property<bool> True while any invocation is in flight
last_error Property<std::optional<Error>> Most recent error, nullopt when OK
last_result Property<std::optional<R>> Most recent successful result (R ≠ void)

Basic Usage

[&worker](std::string query) -> aria::async::Task<SearchResult> {
co_await aria::async::schedule_on(worker);
co_return perform_search(query);
}
};
// Trigger from UI
search.execute("hello");
// Bind in UI
search.is_executing.bind([](bool running) {
spinner.set_visible(running);
});
Definition async_command.hpp:463
void execute(Args... args)
Fire-and-forget.
Definition async_command.hpp:559
Property< bool > & is_executing
Definition async_command.hpp:586

Cancellable Action

Accept a CancellationToken as the first parameter:

co_await aria::async::schedule_on(worker);
tok.throw_if_cancelled();
co_return heavy_load(id);
}
};

Concurrency Policies

The command retains one action object across calls. Mutable captures keep their state between sequential invocations. Parallel, and work still unwinding after LatestOnly cancellation, can invoke this object from different workers; protect shared mutable state or keep every access on the same single-thread executor, including after the action switches executors.

// Parallel (default): multiple invocations run concurrently
ui, worker, save_action, aria::async::AsyncCommandPolicy::Parallel};
// LatestOnly: new execute() cancels in-flight work (search-as-you-type)
ui, worker, search_action, aria::async::AsyncCommandPolicy::LatestOnly};
// DropIfRunning: ignore execute() while busy (prevent double-submit)
ui, worker, refresh_action, aria::async::AsyncCommandPolicy::DropIfRunning};
@ DropIfRunning
silently ignore new invocations while busy
Definition async_command.hpp:125
@ Parallel
default — all invocations run concurrently
Definition async_command.hpp:123
@ LatestOnly
cancel any in-flight invocations before starting
Definition async_command.hpp:124

Inside a ViewModel

class SearchVm : public aria::binding::ViewModel {
public:
: search(ui, worker,
[this](std::string q) -> aria::async::Task<Result> {
co_await aria::async::schedule_on(worker);
co_return do_search(q);
})
{
scope_.attach(*this);
}
aria::Property<std::string> query{""};
aria::async::AsyncCommand<Result, std::string> search;
private:
aria::binding::ViewModelScope scope_;
};
Base class for view models.
Definition view_model.hpp:32
Definition signal.hpp:12
Definition validation_key.hpp:110

ViewModelScope

Ties CoroutineScope to a ViewModel's lifetime. Destroying the VM cancels and joins all in-flight coroutines.

class PollingVm : public aria::binding::ViewModel {
public:
PollingVm() { scope_.attach(*this); }
void start() {
while (!tok.is_cancelled()) {
co_await aria::async::sleep_for(1s);
refresh();
}
});
}
private:
aria::binding::ViewModelScope scope_;
};

When PollingVm is destroyed, scope_ calls cancel_and_join() (default 5 s timeout). Stuck coroutines are reported as leaks.


Combinators

when_all

Wait for all tasks to complete. The result is a tuple; a Task<void> child occupies a std::monostate slot, including when mixed with value-returning tasks:

auto [users, posts, comments] = co_await aria::async::when_all(
load_users(),
load_posts(),
load_comments()
);
}
auto when_all(Task< Ts >... tasks)
Definition when_all.hpp:204

when_any

Complete when the first task finishes (others are cancelled):

co_return co_await aria::async::when_any(
fetch_from_primary(),
fetch_from_backup()
);
}
auto when_any(std::vector< Task< T > > tasks)
Definition when_all.hpp:401

with_timeout

Abort if a task exceeds the deadline:

aria::async::Task<Data> fetch_with_deadline() {
co_return co_await aria::async::with_timeout(
slow_fetch(),
std::chrono::seconds(5)
);
}
auto with_timeout(IDelayedScheduler &timer, std::chrono::milliseconds duration, Factory factory, OnTimeout on_timeout=OnTimeout::Cancel) -> Task< detail::timeout_factory_value_t< Factory > >
Definition timeout.hpp:376

Quick Reference

Type Purpose Produces Value
Task<T> Lazy coroutine Yes (T)
AsyncCommand<R, Args...> Three-state async action Via last_result
CoroutineScope Launch + cancel coroutines No
ViewModelScope Scope tied to VM lifetime No
CancellationToken Cooperative cancellation check No
CancellationSource Cancel producer No
IExecutor Where to run No

See Also