本页目录

async/await 内部机制

async fn 被编译器展开成一个实现了 Future trait 的状态机——每个 .await 点是一个状态。Pin 保证 Future 在内存中不移动(自引用结构),Waker 让 executor 知道何时可以继续轮询。理解了这个展开过程,就理解了 Rust 异步的"零成本抽象"和为什么 Future 不能随意 drop。

async fn 编译后变成什么

async fn read_config() -> Config {
    let file = read_file().await;      // await point 1
    parse(&file).await                 // await point 2
}

编译器将 async fn 转换为实现了 Future trait 的匿名类型——一个状态机:

// 编译器生成的伪代码:
enum ReadConfigFuture {
    State0 { ... },                    // 初始, 即将调 read_file
    State1 { file_content: String },   // 等待 read_file 完成, 持有 file_content
    State2 { file: String },           // 等待 parse 完成
    Done,
}

impl Future for ReadConfigFuture {
    type Output = Config;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Config> {
        match self {
            State0 => { /* start read_file, transition to State1 */ Poll::Pending },
            State1 { file_content } => { /* check if read_file done */ ... },
            // ...
        }
    }
}

关键:每个 .await 点对应一个状态机 transition。状态机存储跨越 await 的局部变量——它们必须在 poll() 返回 Pending 后保持存活,直到下次 poll()

Future trait

pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

executor 循环调用 poll()。如果 Future 还没完成,返回 Poll::Pending。在返回 Pending 之前,Future 必须通过 cx.waker() 注册 waker——一个回调函数,当 Future 可能 ready 时被调用(如 IO 完成、timer 到期)。executor 收到 wake 后重新 poll()

Pin 的作用

状态机中 State1 持有 file_content: String。如果 Future 被 move 到新地址,持有这个地址的内部指针(如果有)会悬垂。对于自引用的 Future(常见于 select! 或涉及 borrowed locals 的场景),Pin<&mut Self> 保证 Future 不会被 move——编译器不生成 memcpy 来移动 pinned 值。

简单 executor 骨架

fn block_on<F: Future>(mut future: F) -> F::Output {
    let waker = ...;                   // 创建 waker
    let mut cx = Context::from_waker(&waker);
    let mut future = unsafe { Pin::new_unchecked(&mut future) };
    loop {
        match future.as_mut().poll(&mut cx) {
            Poll::Ready(v) => return v,
            Poll::Pending => std::thread::park(),  // 等 wake (生产代码用 epoll)
        }
    }
}

这只是概念。真正的 executor(tokio, async-std)用 epoll/kqueue/IOCP 做 IO 多路复用,不是 busy-wait。

参考

  • Rust Book: Async chapter (currently in progress for official book)
  • Tokio tutorial: tokio.rs/tokio/tutorial
  • async-book: rust-lang.github.io/async-book

Keywords: async, await, Future, Pin, poll, state machine, executor, waker