如何在Tauri-Leptos应用中监听后端值变化?
高效监听Windows注册表主题变化(Tauri+Leptos)
你的当前方案用前端无限轮询注册表值,确实会占用大量CPU资源;而直接用std::thread::sleep会阻塞异步任务,tokio::sleep因为运行时上下文不匹配也无法正常工作。更优的方案是后端主动监听注册表变化,通过Tauri事件系统推送给前端,彻底避免轮询。
解决方案步骤
1. 后端实现注册表变化监听
利用Windows原生API RegNotifyChangeKeyValue 监听目标注册表键的修改,当值变化时主动向前端发送事件。
首先确保你的Cargo.toml添加必要依赖:
[dependencies] tauri = { version = "1.5", features = ["api-all"] } winapi = { version = "0.3", features = ["winreg", "handleapi"] } tokio = { version = "1.0", features = ["spawn_blocking"] }
修改后端代码,添加监听逻辑:
use tauri::{AppHandle, Manager}; use winapi::{ shared::minwindef::DWORD, um::{ winreg::{RegCloseKey, RegNotifyChangeKeyValue, RegOpenKeyExW}, }, }; use std::os::windows::ffi::OsStrExt; use std::ptr; use tokio::task; // 原有的is_light_theme命令保留 #[tauri::command] fn is_light_theme() -> String { let theme = helpers::is_light_theme(); format!("{}", theme) } // 启动注册表监听任务 fn start_theme_listener(app_handle: AppHandle) { task::spawn_blocking(move || { let hkey = winapi::um::winreg::HKEY_CURRENT_USER; // 转换为宽字符(Windows API要求) let subkey: Vec<u16> = OsStr::new(r#"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"#) .encode_wide() .chain(std::iter::once(0)) .collect(); let mut key = ptr::null_mut(); // 打开注册表键,需要KEY_NOTIFY权限 let result = unsafe { RegOpenKeyExW( hkey, subkey.as_ptr(), 0, winapi::um::winreg::KEY_QUERY_VALUE | winapi::um::winreg::KEY_NOTIFY, &mut key, ) }; if result != 0 { eprintln!("打开注册表键失败: {}", result); return; } loop { // 阻塞等待注册表变化通知 let result = unsafe { RegNotifyChangeKeyValue( key, 0, // 不监听子键 winapi::um::winreg::REG_NOTIFY_CHANGE_LAST_SET, // 监听值修改 ptr::null_mut(), // 无事件对象,阻塞等待 1, // 通知后继续监听 ) }; if result != 0 { eprintln!("注册注册表变化通知失败: {}", result); break; } // 获取最新主题值并发送给前端 let is_light = helpers::is_light_theme(); let _ = app_handle.emit_all("theme-changed", is_light.to_string()); } // 关闭注册表键 unsafe { RegCloseKey(key); } }); } fn main() { tauri::Builder::default() .setup(|app| { // 启动监听任务 start_theme_listener(app.handle()); Ok(()) }) .invoke_handler(tauri::generate_handler![is_light_theme]) .run(tauri::generate_context!()) .expect("运行Tauri应用失败"); }
2. 前端Leptos监听事件
前端不再需要轮询,只需要初始化时获取一次初始值,然后监听后端发送的theme-changed事件更新信号:
use leptos::*; use tauri::{invoke, listen}; #[component] pub fn App() -> impl IntoView { let (is_light_theme, set_is_light_theme) = create_signal(String::new()); // 获取初始主题值 spawn_local(async move { let initial_value = invoke("is_light_theme", to_value("").unwrap()) .await .as_string() .unwrap_or_else(|| "false".to_string()); set_is_light_theme.set(initial_value); }); // 监听后端的主题变化事件 spawn_local(async move { let _listener = listen("theme-changed", |event| { let new_value = event.payload().unwrap_or("false").to_string(); set_is_light_theme.set(new_value); }) .await .unwrap(); // 保持listener存活,避免被销毁 futures::future::pending().await; }); view! { <div> <p>"当前主题:" {move || is_light_theme.get()}</p> </div> } }
为什么之前的sleep会失效?
你用spawn_local创建的是Wasm异步任务,运行在浏览器的事件循环中:
std::thread::sleep是阻塞线程的操作,会直接卡死Wasm的事件循环,导致后续代码无法执行;tokio::sleep依赖Tokio runtime,而spawn_local属于wasm_bindgen_futures的上下文,两者不兼容,所以无法正常休眠。
内容的提问来源于stack exchange,提问作者Grayza
相关产品推荐
相关产品推荐

