Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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."))
}