首页 / 资讯中心 / 文章详情

【xilem0.4基础语法学与练】第34课 task 异步任务视图

【xilem0.4基础语法学与练】第34课 task 异步任务视图 ★ FEATURED ARTICLE
前言参考官方文档https://docs.rs/xilem/latest/xilem/view/fn.task.html版本Xilem 0.4一、task基础概念task 是一个特殊的视图组件用来在UI树内部运行异步Future任务。普通按钮回调是同步代码如果需要定时循环、网络请求、等待延时这类异步逻辑就使用 task 。核心生命周期规则task 作为View存在于视图树中时异步任务持续运行。一旦 task 从视图树中被移除异步任务就会被自动取消终止不会继续后台跑避免内存泄漏。常用搭配 bool.then(|| Some(task(…))) 布尔为truetask加入视图树任务启动布尔为falsetask被移除任务自动停止。核心交互模型init_future 闭包拿到 MessageProxy 在async异步块中使用 proxy.message(msg) 发送消息M。消息会交给第二个参数 on_event 回调 on_event 可以拿到 mut State 修改应用状态驱动UI刷新。⚠️重要 init_future 异步闭包不能捕获AppState状态变量异步里不能直接读写state全部通信必须走 proxy.message() 发送消息在 on_event 中修改state。区分两个APItask 只要视图还在树上任务就持续运行视图消失自动取消适合循环定时器、长连接。run_once 只执行一次Future执行完成任务就结束适合一次性网络请求。二、函数签名pubfntaskM,F,H,State,Action,Fut(init_future:F,on_event:H,)-TaskF,H,MwhereF:Fn(MessageProxyM)-Fut,Fut:FutureOutput()Sendstatic,H:Fn(mutState,M)-Actionstatic,M:AnyDebugSendstatic,参数说明init_future 初始化异步任务的闭包。入参是 MessageProxy 返回一个async Future。内部写循环、sleep、interval定时器使用 proxy.message(消息实例) 向主线程发送消息。注意这个闭包只在task首次被挂载到视图树执行一次后续UI反复重绘不会重复调用init_future。on_event 消息处理回调。当异步任务调用 proxy.message(M) 发送消息这个回调就会执行。第一个参数 mut State 可以修改应用状态第二个参数M就是异步发过来的消息。MessageProxy关键写法// 发送消息如果返回Err说明task已经被移除任务应当退出letOk(())proxy.message(())else{break;};当 proxy.message() 返回错误代表task视图已经从UI树上移除循环应当break退出异步任务不要继续执行逻辑。需要导入时间工具 use xilem::tokio::time::{interval, Duration};三、最简基础用法定时器计数器// state.running:bool 控制任务是否启用// state.counter:i32 计数state.running.then(||{task(|proxy|asyncmove{letmutintervalinterval(Duration::from_secs(1));loop{interval.tick().await;letOk(())proxy.message(())else{break;};}},|s:mutAppState,_msg|{s.counter1;})})解读runningtrue task挂载视图树每秒发消息on_event让counter1 runningfalse task从树上移除loop自动终止。四、官方示例片段解析 stopwatch.rs秒表data.active.then(||{task(|proxy|asyncmove{letmutintervaltime::interval(Duration::from_millis(50));loop{interval.tick().await;letOk(())proxy.message(())else{break;};}},|data:mutStopwatch,()|{data.update_display();})})布尔 active 控制task是否存在视图树。异步50ms一次tick发送空消息。on_event触发 update_display() 更新秒表内部展示字段UI刷新计时数字。active设置false → task被移除 → proxy.message返回错误 → break循环计时停止。五、完整可运行DemoCargo.toml[package] name xilem_lesson34_task version 0.1.0 edition 2021 [dependencies] xilem 0.4.0 winit 0.30src/main.rsusexilem::winit::error::EventLoopError;usexilem::view::{flex_col,label,text_button,task};usexilem::{EventLoop,WidgetView,WindowOptions,Xilem};usexilem::tokio::time::{interval,Duration};#[derive(Default)]structAppState{timer_running:bool,sec_count:i32,}fnapp_logic(state:mutAppState)-implWidgetViewAppStateuse{flex_col((label(第34课 task异步任务视图),label(format!(计时秒数{},state.sec_count)),text_button(ifstate.timer_running{停止定时器}else{启动定时器},|s:mutAppState|{s.timer_running!s.timer_running;}),text_button(重置计数,|s:mutAppState|{s.sec_count0;}),// timer_running为true才挂载task视图false自动移除任务state.timer_running.then(||{task(|proxy|asyncmove{letmutintervalinterval(Duration::from_secs(1));loop{interval.tick().await;// 发送消息失败就退出循环letOk(())proxy.message(())else{break;};}},|s:mutAppState,_|{s.sec_count1;})}))).padding(12.0)}fnmain()-Result(),EventLoopError{letappXilem::new_simple(AppState::default(),app_logic);app.run(EventLoop::with_user_event(),WindowOptions::default())}六、高频踩坑清单❌不要在init_future异步闭包捕获mut AppState。异步块无法直接访问应用状态所有状态修改必须通过 proxy.message 发送消息在 on_event 回调中修改state。init_future 只会执行一次task挂载视图树的时候执行一次后续UI重绘app_logic反复执行不会重新运行init_future。如果想要每次重绘重启任务要改变视图树条件例如then的布尔值先false再true。异步循环内调用 proxy.message() 一定要判断返回结果返回Err代表task已经被销毁需要break退出loop否则后台循环会悬空。task本身是视图必须放在视图树中不要把task写在按钮回调里面spawntask属于View树节点。task内部Future必须加上 Send static 约束使用 xilem::tokio::time 而不是std::sleep。如果只执行一次性异步操作不是循环优先使用 run_once 而不是task。task更适合持续循环任务。消息类型M可以是自定义结构体不仅仅是 () 可以传递数据从异步给到UI线程。七、课后练习题 标准答案 逐行解读练习1基础需求实现简单闪烁计数器。布尔开关控制task启动停止task每2秒发送消息state.flash_num每次1label展示数值两个按钮启动/停止重置清零。参考答案#[derive(Default)]structAppState{flash_enable:bool,flash_num:i32,}fnapp_logic(state:mutAppState)-implWidgetViewAppStateuse{flex_col((label(format!(闪烁计数{},state.flash_num)),text_button(ifstate.flash_enable{停止}else{启动},|s:mutAppState|s.flash_enable!s.flash_enable),text_button(重置,|s:mutAppState|s.flash_num0),state.flash_enable.then(||{task(|proxy|asyncmove{letmutintvinterval(Duration::from_secs(2));loop{intv.tick().await;letOk(())proxy.message(())else{break;};}},|s:mutAppState,_|{s.flash_num1;})})))}详细解读flash_enable 布尔变量控制task是否加入视图树。当为truetask挂载每两秒发送消息on_event修改flash_num计数。当为falsetask被移出视图树异步检测proxy.message返回错误执行break循环结束任务停止。重置按钮直接修改stateUI同步刷新task异步代码完全不触碰state变量。练习2进阶需求task传递带数据的消息异步任务每秒生成一个随机数字i32通过消息发送给主线程state保存这个数字label展示。开关控制task启停。参考答案#[derive(Default)]structAppState{work:bool,value:i32,}// 自定义消息类型携带异步产生的数据#[derive(Debug)]structNumMsg(i32);fnapp_logic(state:mutAppState)-implWidgetViewAppStateuse{flex_col((label(format!(异步生成数字{},state.value)),text_button(ifstate.work{关闭任务}else{开启任务},|s:mutAppState|s.work!s.work),state.work.then(||{task(|proxy|asyncmove{letmutintvinterval(Duration::from_secs(1));loop{intv.tick().await;letrandom_valrand::random::i32()%100;letOk(())proxy.message(NumMsg(random_val))else{break;};}},|s:mutAppState,msg:NumMsg|{s.valuemsg.0;})})))}详细解读自定义消息结构体 NumMsg(i32) 用来把异步内部计算出的数据传递到UI线程。异步块里面生成随机数通过 proxy.message(NumMsg(random_val)) 发送出去。on_event 回调接收 NumMsg 消息取出内部数字赋值给state.value。task异步闭包中没有访问任何AppState字段全部数据依靠消息中转。注意运行此练习需要在Cargo.toml增加rand依赖 rand “0.8” 。练习3拓展需求模拟下载进度。布尔开关开启tasktask每200ms发送消息携带增量进度。state.progress f64范围0‑1.0配合前面课程学过的 progress_bar 展示进度条。当进度达到1.0自动关闭task提供重置按钮归零。参考答案usexilem::view::progress_bar;#[derive(Default)]structAppState{download_active:bool,progress:f64,}#[derive(Debug)]structProgressInc(f64);fnapp_logic(state:mutAppState)-implWidgetViewAppStateuse{flex_col((label(format!(下载进度 {:.0}%,state.progress*100.0)),progress_bar(Some(state.progress)),text_button(ifstate.download_active{暂停下载}else{开始下载},|s:mutAppState|s.download_active!s.download_active),text_button(重置进度,|s:mutAppState|{s.progress0.0;s.download_activefalse;}),state.download_active.then(||{task(|proxy|asyncmove{letmutintvinterval(Duration::from_millis(200));loop{intv.tick().await;letOk(())proxy.message(ProgressInc(0.05))else{break;};}},|s:mutAppState,msg:ProgressInc|{s.progressmsg.0;ifs.progress1.0{s.progress1.0;s.download_activefalse;}})})))}详细解读task每200ms发送 ProgressInc(0.05) 代表每次增加5%进度。on_event收到消息累加进度当进度≥1.0手动设置 download_activefalse 。download_activefalse 会把task从视图树上移除异步任务自动终止。复用第31课 progress_bar 组件状态驱动UI实现完整模拟下载效果。思考题问题为什么task异步闭包不能直接捕获修改AppState非要通过proxy.message发送消息再on_event修改标准答案解读异步Future运行在后台执行器不在UI主线程直接读写AppState会产生多线程数据竞争Rust编译会直接拒绝。Xilem所有对AppState的修改必须统一在UI主线程回调on_event执行保证状态修改是单线程安全。MessageProxy 提供线程安全消息通道把异步线程产生的数据发送回UI主线程on_event运行在UI主线程安全修改state。task的init_future只负责异步逻辑业务状态更新逻辑全部收拢在on_event回调保证模型一致性。八、知识点总结task 是一个视图组件不是普通spawn任务生命周期绑定视图树视图移除任务自动取消避免内存泄漏。两个闭包分工 init_future 写async异步循环使用 proxy.message(M) 发送消息 on_event 运行在UI主线程接收消息修改AppState。proxy.message() 返回Err的时候代表task已经销毁循环中必须break退出异步任务。异步闭包禁止捕获mut State异步不能直接读写应用状态全部通信走消息传递。常用搭配 bool.then(|| Some(task(…))) 布尔变量控制task挂载/移除启停后台循环任务。消息M可以是自定义结构体可以携带异步产生的数据传给UI层。task适合长生命周期循环任务一次性异步优先选择 run_once 。配合 progress_bar 、label等控件就可以把定时器、模拟IO、后台计算结果渲染到界面。
阅读完成 · 觉得有帮助?
咨询建站