十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

re_async 跨平台异步运行时抽象:Rerun 中如何让同一套代码同时跑在 Tokio 与浏览器上

re_async 跨平台异步运行时抽象:Rerun 中如何让同一套代码同时跑在 Tokio 与浏览器上 re_async 跨平台异步运行时抽象Rerun 中如何让同一套代码同时跑在 Tokio 与浏览器上【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerunRerun 是一个面向多模态机器人数据的可视化、查询与流式传输平台其代码库需要同时支持原生桌面native与 WebAssembly 两种目标。re_async正是为此设计的异步运行时抽象层它向库代码提供统一的AsyncRuntimeHandle把“运行时由谁创建、归谁所有”的决定权交还给应用层从而避免库代码擅自启动 Tokio runtime 造成的线程所有权与生命周期问题。读完本文你将掌握AsyncRuntimeHandle的完整 API、它在原生与 wasm 两种目标下的不同实现策略、配套的跨平台 sleep / yield_now 原语以及基于AsyncReadAt的无状态定位读取设计并看到这些能力在 Rerun 源码中的真实调用方式。一、为什么需要运行时抽象所有权边界问题re_async的定位可以用它自己的 README 一句话概括“Async runtime abstractions for native and WebAssembly targets”即面向原生与 WebAssembly 目标的异步运行时抽象。它属于reruncrate 家族的一部分crates/utils/re_async/README.md。核心设计动机在文档中写得很明确AsyncRuntimeHandle包装的是由应用提供的 executor库代码应当接受一个 handle而不是自己创建 Tokio runtime专用的 runtime 应当归属于进程或线程所有权边界“Dedicated runtimes belong at process or thread ownership boundaries”。也就是说这条抽象解决的问题是所有权问题谁拥有 executor谁负责它的生命周期。库代码如果随手tokio::runtime::Builder::new_multi_thread().build()就会在库内部悄悄引入线程池和资源所有权调用方既无法控制线程数量、调度策略也无法在测试中注入自己可控的 runtime。Rerun 的做法是让库代码声明“我需要异步执行但具体由谁调度由你决定”通过AsyncRuntimeHandle把调度能力注入进来。从 crate 清单crates/utils/re_async/Cargo.toml可以看到这种“双目标”的依赖切分方式仅not(target_arch wasm32)时依赖tokio启用rt、time特性与re_span、async-trait、bytes、thiserror仅target_arch wasm32时依赖futures、js-sys、wasm-bindgen、web-sys启用Window特性docs.rs的文档构建目标被配置为x86_64-unknown-linux-gnu与wasm32-unknown-unknown两个平台进一步印证该 crate 的双平台定位。二、AsyncRuntimeHandle一个句柄两个执行器AsyncRuntimeHandle是re_async的核心类型定义于 crates/utils/re_async/src/lib.rs。它是一个可克隆#[derive(Clone)]的轻量句柄内部在不同目标下持有不同的执行器引用目标平台内部表示行为原生非 wasmtokio::runtime::Handle直接复用调用方已构建的 Tokio runtime 的句柄WebAssembly空结构体通过wasm_bindgen的spawn_local派发到浏览器本地执行器由于结构体字段在不同cfg下不同它提供了两套构造函数保证任意平台都能构造new_native(tokio: tokio::runtime::Handle)包装一个已存在的 Tokio runtime 句柄仅原生目标可用new_web()在 wasm 目标下构造浏览器执行器句柄。此外还有两个通用方法inner()原生目标下取回内部的tokio::runtime::Handlefrom_current_tokio_runtime_or_wasmbindgen()从当前上下文捕获运行时——原生下通过tokio::runtime::Handle::try_current()获取当前线程所在的 runtimewasm 下直接返回浏览器执行器句柄。失败时返回AsyncRuntimeError::TokioError(String)一个基于thiserror的错误类型。这是库代码里最常用的入口因为它不要求调用方显式传参只要“当前恰好运行在某个 Tokio runtime 上下文内”即可。2.1 派生任务spawn_futurespawn_futureF(self, future: F)接受F: FutureOutput () WasmNotSend static用于把“即发即忘”的后台任务投递到执行器上原生下等价于self.tokio.spawn(future)wasm 下等价于spawn_local(future)由浏览器本地执行器调度。注意这里的WasmNotSendtrait它在原生目标下等价于SendimplT: Send WasmNotSend for T在 wasm 下则是对所有类型无条件实现。这样设计是因为 wasm 单线程环境下并不要求 Future 满足Send而原生多线程 Tokio 则需要通过这个条件 traitspawn_future可以在不改变签名的前提下对两种平台的约束各取所需。2.2 测试验证注入外部 runtime 即可直接 spawnlib.rs 内置的原生测试crates/utils/re_async/src/lib.rs 的native_tests模块给出了最直接的用法示范let runtime tokio::runtime::Builder::new_current_thread() .build() .unwrap(); let handle super::AsyncRuntimeHandle::new_native(runtime.handle().clone()); let (sender, receiver) tokio::sync::oneshot::channel(); handle.spawn_future(async move { sender.send(42).ok(); }); assert_eq!(runtime.block_on(receiver), Ok(42));测试名为supplied_runtime_does_not_need_to_be_entered_when_spawning“调用方提供的 runtime 在 spawn 时无需被 enter”验证了关键语义handle 持有的是 runtime 句柄Handlespawn 时并不要求当前线程已经 enter 该 runtime句柄本身足以完成派发。这是库代码可以安全持有AsyncRuntimeHandle并随处 spawn 的基础。2.3 真实调用从 gRPC 流式读取到服务器与下载命令AsyncRuntimeHandle在 Rerun 中被广泛使用以下是几条代表性调用链gRPC 客户端crates/store/re_grpc_client/src/read.rsstream()函数接收AsyncRuntimeHandle内部async_runtime.spawn_future(async move { ... })启动一个后台任务持续从代理服务器读取日志消息并送入re_log_channel通道。这正是“库代码接受 handle 而不是创建 runtime”的典型场景——无论调用者是原生 viewer 还是浏览器版 viewer同一份读取代码都能工作。服务器入口crates/store/re_server/src/entrypoint.rs通过AsyncRuntimeHandle::from_current_tokio_runtime_or_wasmbindgen()?捕获当前 runtime 后启动服务其测试crates/store/re_server/tests/grants.rs也直接使用该构造方式。rerun CLI 下载命令crates/top/rerun/src/commands/download.rs先由应用层构建 Tokio runtime再AsyncRuntimeHandle::new_native(tokio_runtime.clone())注入。查询分析crates/store/re_datafusion/src/dataframe_query_common.rs在连接 DataFusion 查询提供器时调用from_current_tokio_runtime_or_wasmbindgen()捕获运行时用于上报查询指标analytics。re_viewer 重导出crates/viewer/re_viewer/src/lib.rspub use re_async::AsyncRuntimeHandle;表明上层 viewer 把这一句柄作为公共 API 暴露例如在原生 viewer 启动处crates/top/rerun/src/native_viewer.rs用AsyncRuntimeHandle::new_native(runtime.handle().clone())注入。从这些调用可以看到统一模式runtime 由进程边界CLI 入口、viewer 主程序、测试构建然后以 handle 形式注入到各库模块与 README 声明的所有权原则完全一致。三、跨平台异步原语sleep 与 yield_now除了句柄抽象re_async还提供两个跨平台异步函数屏蔽了原生与浏览器在计时器模型上的差异。3.1 sleepTokio 计时器 vs 浏览器计时器队列sleep(duration: Duration)在两个平台上语义相同“至少等待 duration”但实现完全不同原生直接tokio::time::sleep(duration).awaitcrates/utils/re_async/src/lib.rs 中的#[cfg(not(target_arch wasm32))]分支。wasm通过js_sys::Promise调用浏览器的setTimeout把等待委托给浏览器计时器队列。wasm 实现有一个值得注意的细节浏览器计时器使用 32 位有符号毫秒延迟因此更长的时长会被钳制i32::try_from(duration.as_millis()).unwrap_or(i32::MAX)。也就是说在浏览器上超过约 24.8 天的 sleep 会退化为 24.8 天文档与源码注释均对此显式说明。该分支还使用spawn_local_with_result将浏览器 Promise 转换为可 await 的 Future并expect任务不会被取消。3.2 yield_now让出浏览器事件循环yield_now()仅对 wasm 目标有意义crates/utils/re_async/src/lib.rs 的#[cfg(target_arch wasm32)]分支用于让出浏览器事件循环以便其他任务运行。其实现优先使用现代浏览器提供的scheduler.yield()API通过js_sys::Reflect动态探测全局对象上是否存在scheduler.yield若不可用则回退为sleep(Duration::ZERO)。它在实际代码中的作用可以从 crates/store/re_grpc_client/src/read.rs 看到wasm 分支中当 gRPC 消息通道已满TrySendError::Full时循环调用re_async::yield_now().await并且每 10ms 主动 yield 一次避免长时间解码-投递循环独占浏览器主线程导致 UI 卡死。这也是 crates/store/re_datafusion/src/wasm_compat.rs 等处使用re_async::spawn_local_with_result的同类原因wasm 单线程下必须主动让出控制权。3.3 spawn_local 与 spawn_local_with_resultwasm 目标下还有两个底层工具函数spawn_local(future)直接调用js_sys::futures::spawn_local把 Future 投递到浏览器本地执行器crates/utils/re_async/src/lib.rs。spawn_local_with_result(future)把一个可能非Send的浏览器 Future 包装成返回Send结果的 Future返回类型为impl FutureOutput ResultT, TaskCancelled Send static。其内部通过futures::channel::oneshot通道与futures::future::select在“任务完成”与“通道取消”之间竞争从而实现关键语义丢弃返回的 Future 即取消被派发的任务并返回TaskCancelled错误“browser task was canceled”。lib.rs 的 wasm 测试模块web_tests基于wasm_bindgen_test且配置run_in_browser验证了这些语义returns_resultspawn_local_with_result(async { 42 })返回Ok(42)sleepssleep(Duration::ZERO)可正常完成yield_now_allows_other_tasks_to_run通过两个任务验证 yield 之后其他任务得以执行dropping_result_cancels_spawned_future用一个带Drop通知的结构体证明drop 返回的 result Future 后被派发的后台任务确实被取消。四、AsyncReadAt无状态的跨平台定位读取read_at.rscrates/utils/re_async/src/read_at.rs提供了第二个核心抽象异步定位读取。4.1 设计思想无共享游标天然支持并发#[async_trait::async_trait] pub trait AsyncReadAt: Send Sync { async fn read_exact_at(self, span: Spanu64) - io::Resultbytes::Bytes; async fn size(self) - io::Resultu64; }两个关键设计读操作是无状态的read_exact_at以self接收、通过显式span来自re_span::Spanu64包含起始偏移与长度指定读取范围因此单个 reader 可以并发服务多个读取请求无需共享游标返回拥有所有权的bytes::Bytes内存场景下可直接返回底层缓冲区的零拷贝切片Bytes::slice共享底层分配。辅助函数span_len_usize(span)把span.len转换为usize在 wasm 的 32 位指针下若长度放不下则返回io::ErrorKind::InvalidInput“read length does not fit this platforms pointer size”保证错误语义统一。4.2 两个内置实现std::fs::File仅原生使用操作系统的不移动文件偏移量的定位读——Unix 下FileExt::read_atpreadWindows 下FileExt::seek_read——循环填充缓冲区直到读满span.len字节中途遇到 0 字节读取即返回UnexpectedEof“failed to fill whole buffer”。注释还指出了 TODOstd::fs::File::read_exact_at目前在异步 executor 线程上执行阻塞 I/O未来应考虑通过spawn_blocking完成整个定位读。size()通过metadata()?.len()返回文件长度。bytes::Bytes纯内存实现检查范围越界checked_add与end self.len()越界返回UnexpectedEof“read past end of buffer”正常则self.slice(start..end)零拷贝返回子切片size()直接返回缓冲区长度。4.3 在 RRD 解码中的真实用途AsyncReadAt被 RRDRerun 录制数据文件的流式解码链路广泛使用导入器、chunk provider、指纹/页脚读取等模块均基于它做异步定位读见 crates/store/re_log_encoding/src/chunk_provider/rrd.rs、crates/store/re_log_encoding/src/rrd/chunk_reader.rs、crates/store/re_log_encoding/src/rrd/fingerprint.rs、crates/store/re_log_encoding/src/rrd/footer_reader.rs。得益于AsyncReadAt的无状态设计文件、内存缓冲乃至网络来源的字节流可以在同一套解码代码上工作并且从源码结构看这一抽象正是为“从不同字节来源流式解码 RRD”的并发读取需求而设计。五、在 Rerun 中的使用范式总结综合 README 声明与上述源码调用可以总结出re_async在 Rerun 项目中的固定使用范式应用/进程边界创建 runtimeCLI 入口如rerun命令、native viewer 启动、测试使用 Tokio 构建自己的 runtime或直接复用当前上下文。以 handle 注入库代码通过AsyncRuntimeHandle::new_native(handle)或from_current_tokio_runtime_or_wasmbindgen()构造句柄再传给各库函数gRPC 流、DataFusion 查询提供器、服务器、下载命令等。库代码只 spawn 不创建库内部通过handle.spawn_future(...)派发后台任务绝不自己Builder::new_*()建 runtime。wasm 特殊处理浏览器环境使用yield_now()周期性让出主线程、用spawn_local_with_result获取非 Send 任务的 Send 结果并支持取消跨平台代码经cfg(target_arch wasm32)或cfg_select!分流。这套设计让 Rerun 的原生 viewer、web viewer、服务器与 CLI 工具共享同一套异步业务代码同时把 executor 的所有权牢牢控制在每个进程自己的边界之内——这正是re_async在项目中的核心价值一处编写异步业务双平台复用执行语义运行时所有权始终清晰。【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表