在Rust的 Iced框架中,如何使用线程来改变标签的颜色?

编程语言 2026-07-12

我想用Rust(并使用iced 0.12)来实现一个图形界面。它有一个标签,颜色有三种可能:绿色、橙色、红色。颜色应每两秒循环变化(绿色、橙色、红色、绿色、橙色、红色……)。下面是代码。在调试时,我看到线程确实在改变颜色,但图形界面只显示红色。这里缺少了什么?

use iced::widget::{container, text};
use iced::{Application, Command, Element, Settings, Theme, Color, Subscription};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

#[derive(Debug, Clone)]
pub enum Message {
    ColorChanged(ColorState),
}

#[derive(Debug, Clone, Copy, PartialEq)]
enum ColorState {
    Red,
    Orange,
    Green,
}

impl ColorState {
    fn next(self) -> Self {
        match self {
            ColorState::Red => ColorState::Orange,
            ColorState::Orange => ColorState::Green,
            ColorState::Green => ColorState::Red,
        }
    }

    fn to_color(self) -> Color {
        match self {
            ColorState::Red => Color::from_rgb(0.8, 0.0, 0.0),
            ColorState::Orange => Color::from_rgb(1.0, 0.5, 0.0),
            ColorState::Green => Color::from_rgb(0.0, 0.8, 0.0),
        }
    }

    fn to_text(self) -> &'static str {
        match self {
            ColorState::Red => "Red",
            ColorState::Orange => "Orange",
            ColorState::Green => "Green",
        }
    }
}

struct ColorCycleApp {
    current_color: ColorState,
    color_state: Arc<Mutex<ColorState>>,
}

impl Application for ColorCycleApp {
    type Message = Message;
    type Theme = Theme;
    type Executor = iced::executor::Default;
    type Flags = ();

    fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
        let color_state = Arc::new(Mutex::new(ColorState::Red));

        // Start the background thread
        let color_state_clone = Arc::clone(&color_state);
        thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(2));

                let mut state = color_state_clone.lock().unwrap();
                *state = state.next();
                println!("Thread changed color to: {:?}", *state);
            }
        });

        (
            Self {
                current_color: ColorState::Red,
                color_state,
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        String::from("My GUI")
    }

    fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
        match message {
            Message::ColorChanged(new_color) => {
                self.current_color = new_color;
                println!("New color: {:?}", new_color);
                Command::none()
            }
        }
    }

    fn view(&self) -> Element<Self::Message> {
        let current_color = self.current_color.to_color();

        let label = text(self.current_color.to_text())
            .size(48);

        let colored_container = container(label)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .center_x()
            .center_y()
            .style(move |_theme: &Theme| container::Appearance {
                background: Some(iced::Background::Color(current_color)),
                ..Default::default()
            });

        colored_container.into()
    }

    fn subscription(&self) -> Subscription<Self::Message> {
        let color_state = Arc::clone(&self.color_state);

        iced::subscription::unfold(
            "color_monitor",
            ColorState::Red,
            move |last_color| {
                let color_state = Arc::clone(&color_state);
                async move {
                    loop {
                        tokio::time::sleep(Duration::from_millis(100)).await;

                        let current_color = {
                            let state = color_state.lock().unwrap();
                            *state
                        };

                        if current_color != last_color {
                            return (Message::ColorChanged(current_color), current_color);
                        }
                    }
                }
            }
        )
    }
}

fn main() -> iced::Result {
    ColorCycleApp::run(Settings {
        window: iced::window::Settings {
            size: iced::Size::new(400.0, 300.0),
            ..Default::default()
        },
        ..Default::default()
    })
}

解决方案

问题就是这点:

thread '<unnamed>' (23476723) panicked at src/main.rs:123:25:
there is no reactor running, must be called from the context of a Tokio 1.x runtime
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

这由 tokio::time::sleep(Duration::from_millis(100)).await; 引起。

我并不完全理解你在这里为什么要使用 tokio,但如果你确实需要,你需要一个 reactor,如错误信息所述。然而这并不容易通过 iced 实现,因为它的 async 执行器基于 futuresasync-std 风格的原语,并且并非建立在 tokio 之上。

最简单的修复是直接使用 std::thread::sleep 来替代。
这是应用了修正(以及一些其他小改动)的完整代码:

use iced::widget::{container, text};
use iced::{Application, Color, Command, Element, Settings, Subscription, Theme};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

#[derive(Debug, Clone)]
pub(crate) enum Message {
    ColorChanged(ColorState),
}

#[derive(Debug, Clone, Copy, PartialEq)]
enum ColorState {
    Red,
    Orange,
    Green,
}

impl ColorState {
    fn next(self) -> Self {
        match self {
            ColorState::Red => ColorState::Orange,
            ColorState::Orange => ColorState::Green,
            ColorState::Green => ColorState::Red,
        }
    }

    fn to_color(self) -> Color {
        match self {
            ColorState::Red => Color::from_rgb(0.8, 0.0, 0.0),
            ColorState::Orange => Color::from_rgb(1.0, 0.5, 0.0),
            ColorState::Green => Color::from_rgb(0.0, 0.8, 0.0),
        }
    }

    fn to_text(self) -> &'static str {
        match self {
            ColorState::Red => "Red",
            ColorState::Orange => "Orange",
            ColorState::Green => "Green",
        }
    }
}

struct ColorCycleApp {
    current_color: ColorState,
    color_state: Arc<Mutex<ColorState>>,
}

impl Application for ColorCycleApp {
    type Message = Message;
    type Theme = Theme;
    type Executor = iced::executor::Default;
    type Flags = ();

    fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
        let color_state = Arc::new(Mutex::new(ColorState::Red));

        // Start the background thread
        let color_state_clone = Arc::clone(&color_state);
        thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(2));

                let mut state = color_state_clone.lock().unwrap();
                *state = state.next();
                println!("Thread changed color to: {:?}", *state);
            }
        });

        (
            Self {
                current_color: ColorState::Red,
                color_state,
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        String::from("My GUI")
    }

    fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
        match message {
            Message::ColorChanged(new_color) => {
                self.current_color = new_color;
                println!("New color: {:?}", new_color);
                Command::none()
            }
        }
    }

    fn view(&'_ self) -> Element<'_, Self::Message> {
        let current_color = self.current_color.to_color();

        let label = text(self.current_color.to_text()).size(48);

        let colored_container = container(label)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .center_x()
            .center_y()
            .style(move |_theme: &Theme| container::Appearance {
                background: Some(iced::Background::Color(current_color)),
                ..Default::default()
            });

        colored_container.into()
    }

    fn subscription(&self) -> Subscription<Self::Message> {
        let color_state = Arc::clone(&self.color_state);

        iced::subscription::unfold("color_monitor", ColorState::Red, move |last_color| {
            let color_state = Arc::clone(&color_state);
            async move {
                loop {
                    std::thread::sleep(Duration::from_millis(100));

                    let current_color = {
                        let state = color_state.lock().unwrap();
                        *state
                    };

                    if current_color != last_color {
                        return (Message::ColorChanged(current_color), current_color);
                    }
                }
            }
        })
    }
}

fn main() -> iced::Result {
    ColorCycleApp::run(Settings {
        window: iced::window::Settings {
            size: iced::Size::new(400.0, 300.0),
            ..Default::default()
        },
        ..Default::default()
    })
}

替代方案

如果你确实希望保留 async-await 风格的sleep机制,你可以改用 futures-timer crate,并依赖类似下面这样的实现:

futures_timer::Delay::new(Duration::from_millis(100)).await;
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章