如何在Rust WASM应用中轮询Future?
如何在Rust WASM应用中轮询Future?
嘿,我刚好碰到过类似的场景——用reqwest在WebXR渲染循环里搞异步请求,不想阻塞渲染流程对吧?手动调用poll确实有点绕,我来给你拆解一下怎么弄,还有更省心的替代方案。
首先得搞明白:直接手动轮询Future不是最推荐的方式,但如果你的场景必须这么做,核心是要搞定Context里的Waker——这玩意儿负责在Future有进展的时候通知程序再来poll它。在WASM环境里,这个Waker得和浏览器的事件循环绑定,不然你的请求可能永远卡在Pending状态。
手动轮询的步骤(适合一定要自己控制时机的场景)
首先得引入必要的依赖,Cargo.toml里加上这些:
[dependencies] reqwest = { version = "0.11", features = ["wasm-bindgen"] } wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" futures = "0.3" log = "0.4" # 用来打日志看状态
然后写代码的思路是:把Future存起来(得用Pin<Box<...>>,因为Future poll的时候不能随便移动内存),每次渲染循环里拿出来poll一下,根据结果处理:
use futures::Future; use std::pin::Pin; use wasm_bindgen::prelude::*; use reqwest::Client; // 你的应用结构体,存pending的请求和其他渲染状态 #[wasm_bindgen] pub struct XRApp { pending_request: Option<Pin<Box<dyn Future<Output = Result<reqwest::Response, reqwest::Error>>>>>, // 比如这里可以加WebXR的渲染上下文之类的 } #[wasm_bindgen] impl XRApp { pub fn new() -> Self { // 初始化reqwest客户端 let client = Client::new(); // 发起请求,得到一个Future let request_future = client.get("https://your-api-url.com/data").send(); // 把Future装箱并Pin住,存到结构体里 XRApp { pending_request: Some(Box::pin(request_future)), } } pub fn render_frame(&mut self) { // 检查有没有待处理的请求 if let Some(mut future) = self.pending_request.take() { // 获取和浏览器事件循环绑定的Waker,这步很关键! let waker = wasm_bindgen_futures::queue::queue().waker(); let mut ctx = std::task::Context::from_waker(&waker); // 开始轮询Future match future.as_mut().poll(&mut ctx) { std::task::Poll::Ready(result) => { // 请求完成啦,处理结果 match result { Ok(response) => { log::info!("请求成功!状态码: {}", response.status()); // 这里可以把响应数据存起来,比如转成JSON存到结构体里,后面渲染用 } Err(e) => { log::error!("请求炸了: {}", e); } } // 请求处理完,清空pending状态 self.pending_request = None; } std::task::Poll::Pending => { // 请求还没好,把Future放回结构体,下次渲染再试 self.pending_request = Some(future); } } } // 这里该干嘛干嘛,正常渲染WebXR内容 // ... } }
这里要注意几个点:
Pin<Box<dyn Future>>:必须用这个,因为Future的poll方法要求它不能被移动,Pin就是用来固定它的内存位置的。Waker:用wasm_bindgen_futures提供的queue获取的Waker,会把Future的唤醒事件绑定到浏览器的事件循环,这样当reqwest的请求有进展时,浏览器会自动安排下一次poll,不会让你一直空等。
更省心的替代方案(推荐)
其实手动轮询有点折腾,Rust WASM的生态已经帮我们做好了更简单的方式——用wasm_bindgen_futures::spawn_local把Future丢给浏览器的事件循环去处理,等请求完成后更新应用状态,渲染循环只需要读状态就行:
#[wasm_bindgen] pub struct XRApp { fetched_data: Option<String>, // 其他渲染状态 } #[wasm_bindgen] impl XRApp { pub fn new() -> Self { let mut app = XRApp { fetched_data: None }; let client = Client::new(); // 把请求Future spawn到浏览器事件循环里 wasm_bindgen_futures::spawn_local(async move { match client.get("https://your-api-url.com/data").send().await { Ok(response) => { if let Ok(text) = response.text().await { // 请求完成,更新应用状态 app.fetched_data = Some(text); } } Err(e) => { log::error!("请求失败: {}", e); } } }); app } pub fn render_frame(&self) { // 渲染的时候检查有没有拿到数据,有的话就画出来 if let Some(data) = &self.fetched_data { log::info!("要渲染的数据: {}", data); // 这里就可以把data转换成你要画的图形啦 } // 正常渲染WebXR内容 // ... } }
这种方式不用管什么poll和Context,完全交给async生态处理,代码简洁多了,也不容易出错。
总的来说,如果不是特别需要精确控制poll的时机,优先用spawn_local的方式;如果必须自己在渲染循环里控制,就按照第一个方案来,核心是搞定Waker和Pin的问题。
备注:内容来源于stack exchange,提问作者Mutant Bob
相关产品推荐
相关产品推荐

