Skip to content

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::fsasync-std::fs 等真正的异步 I/O 实现来配合 IoTaskPool

与 async 生态桥接

IoTaskPool 内部使用 async_executor,可以运行任何 Future。这意味着你可以把 reqwestsqlx 等异步库的任务直接 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:

  1. 启动时创建 channel,把 SenderReceiver 存为 Resource
  2. 后台任务 clone Sender,完成后 send
  3. 主线程系统每帧 try_iter() 接收结果

参见 Listing 34-6(src/main.rs)的完整示例。