Skip to content

Tasks

HuxerUI Task<T> is a lazy C++ coroutine type. Launch top-level work through a scoped TaskScope so unmount cancels outstanding operations.

Task<void> LoadProfile(State<std::string> status) {
status = "Loading";
co_await Delay(std::chrono::milliseconds(250));
status = "Ready";
}

A task does not begin merely because its value was created. It begins when awaited or launched by an owning scope.

[[huxerui::composable]]
View LoadButton(State<std::string> status) {
auto tasks = UseTaskScope();
return Button("Load").OnClick([tasks, status] {
tasks.Launch(LoadProfile(status));
});
}

TaskScope::Launch accepts a Task<void> or a factory returning one and returns a TaskHandle. The scope cancels all active handles when its component unmounts; explicit Cancel() is idempotent.

Capture State and service handles by value. Do not retain raw View objects or platform objects across suspension unless their API explicitly provides that lifetime.

Tasks may await other HuxerUI tasks. Delay resumes through the platform scheduler, and service operations such as HTTP and files return tasks that follow the same continuation model.

Exceptions propagate through co_await. Catch failures at the boundary that can present a useful result, and make cancellation a normal control-flow outcome rather than a detached background exception.

Create the TaskScope in the smallest scoped component that owns the result. A page-level refresh belongs to the page scope; a row-level thumbnail load belongs to the keyed row scope. Moving the scope higher keeps work alive longer, while moving it lower cancels work as soon as that feature leaves composition.

Keep the returned TaskHandle only when the user needs an explicit cancel or a newer request must supersede an older one. Calling Cancel() more than once or after completion is safe. Scope destruction remains the final backstop, so a forgotten handle does not detach the coroutine from UI lifetime.

For repeated requests, decide which result wins. Cancel the previous handle for “latest request wins,” or associate a generation/key with the request and ignore a stale completion. Do not let an older response overwrite newer controlled state merely because it completed later.

The coroutine may resume after composition has run many times. Captured State<T> and service handles are stable value handles; transient View values and mounted-node pointers are not. Read request inputs before launch when one operation must use a snapshot, and read state after suspension only when the latest value is intentionally required.

Cancellation stops continuation of work owned by the HuxerUI execution, but a platform operation may still need time to release its native request. Do not use cancellation as a transaction rollback; commit external mutations only where the underlying service defines that guarantee.

Composition declares current UI and must not block on I/O. Launch work from an event, lifecycle setup, activation handler, or another task. Update controlled state with the result so a later composition declares the new interface.

Do not detach raw coroutines that retain component state. See the Task API and the asynchronous HTTP and Files services.

Await RunWorker(function, arguments...) for non-UI synchronous computation. It owns its callable/arguments and returns to the awaiting UI task after completion. Do not access State or platform UI from the worker; copy the input snapshot before launching and apply the result after awaiting.

Use WorkerSequence only when related operations must run serially, such as a decoder or ordered file transformation. It gives ordering rather than permanent thread affinity. Its leading stop token makes active cancellation cooperative.

TaskScope::Post is reserved for callbacks from external threads. HTTP, Files, Delay, and RunWorker continuations already execute in the correct task context. See the worker example.