I/O 任务:IoTaskPool
文件读写、网络请求等 I/O 操作的特征是:大部分时间花在等待外部响应,CPU 占用很低。IoTaskPool 专门用于这类任务。
rust
use bevy::prelude::*;
use bevy::tasks::IoTaskPool;
use std::path::PathBuf;
fn main() {
App::new()
.add_plugins(MinimalPlugins)
.add_systems(Startup, write_then_read)
.run();
}
fn write_then_read() {
let pool = IoTaskPool::get();
// 写入任务
let path = PathBuf::from("target/_ch34_demo.txt");
let content = "Hello from IoTaskPool!\n这是异步 I/O 写入的内容。".to_string();
let write_path = path.clone();
pool.spawn(async move {
std::fs::create_dir_all(write_path.parent().unwrap()).unwrap();
std::fs::write(&write_path, &content).unwrap();
info!("文件写入完成: {}", write_path.display());
})
.detach();
// 读取任务
let read_path = path.clone();
pool.spawn(async move {
match std::fs::read_to_string(&read_path) {
Ok(text) => info!("文件读取成功:\n{text}"),
Err(e) => warn!("读取失败: {e}"),
}
})
.detach();
}Listing 34-3:IoTaskPool——异步文件写入与读取
虽然 std::fs::write 本身是同步的,但把它放进 IoTaskPool 后,阻塞的是 I/O 线程而不是主线程。在实际项目中,应该用 tokio::fs 或 async-std::fs 等真正的异步 I/O 实现来配合 IoTaskPool。
与 async 生态桥接
IoTaskPool 内部使用 async_executor,可以运行任何 Future。这意味着你可以把 reqwest、sqlx 等异步库的任务直接 spawn 到 IoTaskPool 上:
rust
let pool = IoTaskPool::get();
pool.spawn(async move {
let resp = reqwest::get("https://example.com").await.unwrap();
let body = resp.text().await.unwrap();
// 通过 channel 把结果送回 ECS
}).detach();关键:异步库的 runtime 和 Bevy 的 runtime 可能不同。如果用 tokio 的异步库,确保它的 future 是 Send + 'static,或者在 IoTaskPool 上运行时不需要 tokio runtime。
channel 模式
后台任务完成后,需要把结果送回 ECS 主线程。最常用的模式是 crossbeam channel:
- 启动时创建 channel,把
Sender和Receiver存为 Resource - 后台任务 clone
Sender,完成后 send - 主线程系统每帧
try_iter()接收结果
参见 Listing 34-6(src/main.rs)的完整示例。