Blocking Code
Note: Currently, it is not possible to run blocking code on wasm.
Tokio
If your iced application is using the Tokio
runtime, to run blocking code in a Task
or a Subscription
we can use tokio::task::spawn_blocking
.
Example
Here is a small example that shows how to use tokio::task::spawn_blocking
.
Cargo.toml
Because we want to use spawn_blocking
from tokio we need to add the tokio
feature to iced. This will lead to iced using tokio.
iced = { ... , features = ["tokio", ...] }
Actual Example
In the example, there will be a button and a text. A press onto the button will trigger a large computation to be started (in the example, we will just sleep a few seconds and return a number). If the computation finishes, the result will be shown in the text widget.
Our computation runs in a task, because we do not want to block our whole UI until it has finished.
Inside the task we call spawn_blocking
with a closure of our computation. To get the returned value of the closure, we need to await the JoinHandle
returned by spawn_blocking
.
That will give us the result of the heavy computation without blocking the UI.
#[derive(Debug, Clone)]
enum Message {
CalculatedInformation(i32),
StartCalculatingInformation,
}
#[derive(Default)]
struct App {
hard_to_process_information: Option<i32>,
calculation_in_progress: bool,
}
impl App {
fn update(&mut self, message: Message) -> iced::Task<Message> {
match message {
Message::CalculatedInformation(information) => {
// Set the information
self.hard_to_process_information = Some(information);
self.calculation_in_progress = false;
}
Message::StartCalculatingInformation => {
// Change the state to indicate that the calculation is in progress
self.calculation_in_progress = true;
// Return a task that will calculate the information
return iced::Task::future(async {
let information = tokio::task::spawn_blocking(|| {
println!("Calculating information...");
// Simulate a long computation
std::thread::sleep(std::time::Duration::from_secs(2));
// return some value
42
})
.await
.unwrap();
// Send the information back to the update function
Message::CalculatedInformation(information)
});
}
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
iced::widget::column![
// Display the information if it is available
iced::widget::Text::new(self.hard_to_process_information.map_or(
"Information will be ready in a second...".to_string(),
|x| format!("Information: {x}"),
)),
// Display a button to start the calculation
iced::widget::button("Start Calculation").on_press_maybe(
if self.calculation_in_progress {
None
} else {
Some(Message::StartCalculatingInformation)
}
)
]
.into()
}
}
fn main() {
iced::run("Task Example", App::update, App::view).unwrap();
}
Full Code
#[derive(Debug, Clone)]
enum Message {
CalculatedInformation(i32),
StartCalculatingInformation,
}
#[derive(Default)]
struct App {
hard_to_process_information: Option<i32>,
calculation_in_progress: bool,
}
impl App {
fn update(&mut self, message: Message) -> iced::Task<Message> {
match message {
Message::CalculatedInformation(information) => {
// Set the information
self.hard_to_process_information = Some(information);
self.calculation_in_progress = false;
}
Message::StartCalculatingInformation => {
// Change the state to indicate that the calculation is in progress
self.calculation_in_progress = true;
// Return a task that will calculate the information
return iced::Task::future(async {
let information = tokio::task::spawn_blocking(|| {
println!("Calculating information...");
// Simulate a long computation
std::thread::sleep(std::time::Duration::from_secs(2));
// return some value
42
})
.await
.unwrap();
// Send the information back to the update function
Message::CalculatedInformation(information)
});
}
}
iced::Task::none()
}
fn view(&self) -> iced::Element<'_, Message> {
iced::widget::column![
// Display the information if it is available
iced::widget::Text::new(self.hard_to_process_information.map_or(
"Information will be ready in a second...".to_string(),
|x| format!("Information: {x}"),
)),
// Display a button to start the calculation
iced::widget::button("Start Calculation").on_press_maybe(
if self.calculation_in_progress {
None
} else {
Some(Message::StartCalculatingInformation)
}
)
]
.into()
}
}
fn main() {
iced::run("Task Example", App::update, App::view).unwrap();
}
Smol
If your iced application is using the Smol
runtime, you can use smol::unblock
.
Add smol
to cargo.toml:
smol = "2"
#[derive(Debug, Clone)]
struct MeaningOfLife(String);
async fn meaning_of_life() -> MeaningOfLife {
smol::unblock(|| calculate_meaning()).await
}
fn calculate_meaning() -> MeaningOfLife {
std::thread::sleep(Duration::from_millis(3000));
MeaningOfLife(String::from("The meaning of life is 42."))
}
Oneshot Channel
Another way to run blocking code is to use a oneshot
channel:
use iced::widget::{button, center, text};
use iced::{Element, Task};
use std::time::Duration;
fn main() -> iced::Result {
iced::application("Find the meaning of life", App::update, App::view).run()
}
#[derive(Debug, Clone)]
enum Message {
FindTheMeaningOfLife,
TheMeaningOfLife(Option<MeaningOfLife>),
}
#[derive(Default, Debug)]
enum State {
#[default]
Unknown,
Searching,
Found(MeaningOfLife),
NotFound,
}
#[derive(Debug, Default)]
struct App {
state: State,
}
impl App {
pub fn view(&self) -> Element<'_, Message> {
let main: Element<_> = match &self.state {
State::Unknown => {
button("Find the meaning of life.")
.on_press(Message::FindTheMeaningOfLife)
.into()
},
State::Searching => text("Searching...").into(),
State::Found(MeaningOfLife(meaning)) => text(meaning).into(),
State::NotFound => text("Could not find the meaning of life.").into(),
};
center(main).into()
}
pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::FindTheMeaningOfLife => {
self.state = State::Searching;
Task::perform(
meaning_of_life(),
Message::TheMeaningOfLife
)
}
Message::TheMeaningOfLife(meaning) => {
self.state = match meaning {
Some(meaning) => State::Found(meaning),
None => State::NotFound,
};
Task::none()
}
}
}
}
#[derive(Debug, Clone)]
struct MeaningOfLife(String);
async fn meaning_of_life() -> Option<MeaningOfLife> {
use iced::futures::channel;
// Create a oneshot channel for the thread to send its result.
let (result_tx, result_rx) = channel::oneshot::channel();
// Spawn a thread so that our calculation doesn't block the main thread.
std::thread::spawn(|| {
let result = calculate_meaning();
result_tx
.send(result)
.expect("Showing the meaning of life");
});
// Wait for our result to arrive.
result_rx.await.ok()
}
fn calculate_meaning() -> MeaningOfLife {
// Super long and complicated calculation.
std::thread::sleep(Duration::from_millis(3000));
MeaningOfLife(String::from("The meaning of life is 42."))
}
A oneshot channel provides a type-safe way of sending a single value between threads / asynchronous tasks.
Furthermore, this approach does not require any extra dependencies or features as iced conveniently re-exports the futures
crate.
Complete Example
use iced::widget::{button, center, text};
use iced::{Element, Task};
use std::time::Duration;
fn main() -> iced::Result {
iced::application("Find the meaning of life", App::update, App::view).run()
}
#[derive(Debug, Clone)]
enum Message {
FindTheMeaningOfLife,
TheMeaningOfLife(Option<MeaningOfLife>),
}
#[derive(Default, Debug)]
enum State {
#[default]
Unknown,
Searching,
Found(MeaningOfLife),
NotFound,
}
#[derive(Debug, Default)]
struct App {
state: State,
}
impl App {
pub fn view(&self) -> Element<'_, Message> {
let main: Element<_> = match &self.state {
State::Unknown => {
button("Find the meaning of life.")
.on_press(Message::FindTheMeaningOfLife)
.into()
},
State::Searching => text("Searching...").into(),
State::Found(MeaningOfLife(meaning)) => text(meaning).into(),
State::NotFound => text("Could not find the meaning of life.").into(),
};
center(main).into()
}
pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::FindTheMeaningOfLife => {
self.state = State::Searching;
Task::perform(
meaning_of_life(),
Message::TheMeaningOfLife
)
}
Message::TheMeaningOfLife(meaning) => {
self.state = match meaning {
Some(meaning) => State::Found(meaning),
None => State::NotFound,
};
Task::none()
}
}
}
}
#[derive(Debug, Clone)]
struct MeaningOfLife(String);
async fn meaning_of_life() -> Option<MeaningOfLife> {
use iced::futures::channel;
// Create a oneshot channel for the thread to send its result.
let (result_tx, result_rx) = channel::oneshot::channel();
// Spawn a thread so that our calculation doesn't block the main thread.
std::thread::spawn(|| {
let result = calculate_meaning();
result_tx
.send(result)
.expect("Showing the meaning of life");
});
// Wait for our result to arrive.
result_rx.await.ok()
}
fn calculate_meaning() -> MeaningOfLife {
// Super long and complicated calculation.
std::thread::sleep(Duration::from_millis(3000));
MeaningOfLife(String::from("The meaning of life is 42."))
}