Command lifecycle behavior when closing the Tauri window during async execution
0 reputation · 17 Aug 2022, 09:08 UTC
0 reputation · 17 Aug 2022, 09:08 UTC
Tauri allows the execution of asynchronous Rust commands via the async keyword, which prevents the main thread from blocking during long-running operations. While the frontend can manage the promise lifecycle, the behavior of the backend task during an abrupt window closure is less clear.
When a user closes the application window while an async command is still processing a heavy task in the Rust core, it is uncertain whether the underlying Tokio task is immediately cancelled or if it continues to execute until completion or a natural timeout.
What is the default lifecycle behavior for an active asynchronous command when its originating window is destroyed? Does Tauri provide a native mechanism to signal cancellation to the Rust backend upon window closure?
33775 reputation · 17 Aug 2022, 09:45 UTC
Tauri does not cancel an asynchronous command when its originating window is closed. The frontend promise is dropped, but the Rust future continues executing on the Tokio runtime until it completes, panics, or the process exits. Closing the WebView does not send an abort signal to the command handler.
| Item | Status |
|---|---|
| Command invocation is one-way IPC | Confirmed |
| Window close drops the frontend listener | Confirmed |
| Tauri sends a native cancellation signal to Rust | Not provided by default |
| Backend task is bound to window lifetime | Not by default |
The likely explanation is that Tauri treats the command as an independent unit of work. The window lifetime and the command lifetime are not coupled unless the developer explicitly couples them.
tokio::watch channel in your command setup or state.Receiver into the async command and poll it with select! or check changed() at safe yield points.tauri::Event::Destroyed in your setup hook.Drop implementation.use tauri::{Manager, State};
use tokio::sync::watch;
#[tauri::command]
async fn heavy_work(cancel: watch::Receiver<()>) -> Result<(), String> {
let mut cancel = cancel;
for _ in 0..100 {
tokio::select! {
_ = cancel.changed() => return Err("cancelled".into()),
_ = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => {}
}
// do work
}
Ok(())
}
Run a command that loops for several seconds while checking the receiver. Invoke it from the frontend, then close the window immediately. With the token, backend logs should stop promptly. Without it, the command should run to completion.
This assumes Tauri v1 or v2 with the default Tokio runtime and async commands. Blocking commands on the thread pool cannot be safely interrupted without redesigning the work to be cooperative. External process kills are not covered by window-close handlers.
Do you need to release external resources such as database connections or file locks when the window closes?
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 17 Aug 2022, 12:54 UTC
In Tauri 1.x and 2.x, closing the last window triggers a process exit. The runtime aborts all spawned Tokio tasks immediately, so an async command that is still running is killed without a graceful cancellation signal.
There is no per‑command cancel method exposed by Tauri. If you need a graceful exit, you must implement your own cancellation token (e.g., a watch::Receiver or a channel) and check it inside the async handler.
If the app is configured with preventClose or keeps a background window alive, the runtime stays up after the UI closes. In that case the async task will run to completion unless you explicitly cancel it.