Asynchronous tasks
Run cancellable work, keep completion on the UI thread, and prevent stale results.
Enable tasks explicitly
The tasks feature uses Tokio on native targets and browser-local futures on WebAssembly. Applications without it do not pull in Tokio.
[dependencies]
argui = { version = "0.3.2", features = ["tasks", "widget-input", "widget-button", "widget-vlist"] }Replace stale work
spawn_latest stores ownership in a TaskSlot. Starting a new search cancels the previous delivery, including an older result already queued for the UI thread.
fn search(&mut self, cx: &mut Context<Self>) -> Result<(), TaskError> {
let query = self.query.clone();
cx.spawn_latest(&mut self.task, load_results(query), |model, result, cx| {
match result {
Ok(Ok(items)) => model.results = items,
Ok(Err(error)) => model.error = Some(error),
Err(error) => model.error = Some(error.to_string()),
}
cx.notify();
})?;
Ok(())
}Choose the right owner
ModelContext::spawn binds work to model lifetime. Context::spawn additionally binds it to one presentation. Dropping the handle, closing its scope, unmounting its owner, or shutting down the runtime cancels delivery.
Reference files
These are the implementation and guide files used for this chapter.
crates/argui-widget-gallery/src/pages/async_tasks.rsdocs/runtime/tasks.mdCancellable search over 10,000 rows
The Rust file below is imported verbatim by this page and compiled into the WebAssembly application running underneath it.
use std::time::Duration;
use argui::{
runtime::{
Context, Render,
tasks::{TaskHandle, sleep},
},
text::TextStyle,
ui::{Element, Sides, percent},
widgets::{Button, default_theme},
};
#[derive(Default)]
pub struct Example {
result: Option<String>,
task: Option<TaskHandle>,
}
impl Render for Example {
fn render(&mut self, cx: &mut Context<Self>) -> Element {
let themes = default_theme(cx.environment());
let theme = themes.resolve(cx.environment().color_scheme);
let status = self.result.as_deref().unwrap_or("No task running");
Element::column([
Button::new("load", "Load asynchronously", theme.button())
.enabled(self.task.is_none())
.on_click(cx.event_handler(|app, _, cx| {
app.result = Some("Loading…".into());
app.task = cx
.spawn(
async { sleep(Duration::from_millis(650)).await },
|app, result, cx| {
result.expect("the local timer completes");
app.result = Some("Loaded without blocking the UI".into());
app.task = None;
cx.notify();
},
)
.ok();
cx.notify();
}))
.build(),
Element::text(status).text_style(TextStyle {
color: theme.foreground,
..TextStyle::default()
}),
])
.width(percent(1.0))
.height(percent(1.0))
.padding(Sides::length(28.0))
.gap(16.0)
.background(theme.background)
}
}
Starting this lesson’s WebAssembly module…