2. The Future Trait 🟡
你将学到:
Futuretrait:Output、poll()、Context、Waker- Waker 如何告诉 executor “再次 poll 我”
- 契约:从不调用
wake()= 你的程序静默挂起- 手动实现一个真实的 future(
Delay)
Future 的解剖
Rust 中的一切异步最终都实现这个 trait:
#![allow(unused)]
fn main() {
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
pub enum Poll<T> {
Ready(T), // Future 已完成,返回值 T
Pending, // Future 还未就绪——稍后再次调用我
}
}
就是这样。Future 是任何可以被 poll 的东西——被问“你完成了吗?“——然后回答“是的,这是结果“或“还没,我会在准备好时唤醒你。”
Output、poll()、Context、Waker
sequenceDiagram
participant E as Executor
participant F as Future (Task)
participant OS as Operating System<br/>(e.g., epoll/kqueue)
participant R as Reactor (Runtime)
E->>F: 调用 poll(cx)
Note right of F: Future 尝试操作
F->>OS: 系统调用 (e.g., 读取 TCP socket)
OS-->>F: 返回错误:未就绪
F->>R: 注册:(Waker)
F-->>E: 返回 Poll::Pending
Note left of E: Task 被移出<br/>运行队列
E->>E: (Executor 运行其他任务或睡眠)
R->>OS: epoll_wait() / Poll 询问 OS 事件
Note right of OS: (稍后) 新数据到达
OS-->>R: 唤醒 Reactor:数据现在就绪
R->>R: Reactor 找到 Waker
R->>E: 调用 Waker::wake()
Note right of E: Task 被推回<br/>Executor 的运行队列
E->>F: 再次调用 poll(cx)
Note right of F: Future 再次尝试操作
F->>OS: 系统调用 (e.g., 读取 TCP socket)
OS-->>F: 成功:返回数据缓冲区
F-->>E: 返回 Poll::Ready(Data)
让我们分解每个部分:
#![allow(unused)]
fn main() {
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
// 一个立即返回 42 的 future
struct Ready42;
impl Future for Ready42 {
type Output = i32; // Future 最终产生的值
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<i32> {
Poll::Ready(42) // 总是就绪——无需等待
}
}
}
组件:
Output—— future 完成时产生的值的类型poll()—— 由 executor 调用来检查进度;返回Ready(value)或PendingPin<&mut Self>—— 确保 future 不会在内存中移动(我们将在第 4 章解释原因)Context—— 携带Waker以便 future 在准备好继续执行时向 executor 发信号
Waker 契约
Waker 是回调机制。当 future 返回 Pending 时,它必须安排好稍后调用 waker.wake()——否则 executor 再也不会 poll 它,程序就会挂起。
#![allow(unused)]
fn main() {
use std::task::{Context, Poll, Waker};
use std::pin::Pin;
use std::future::Future;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
/// 一个在延迟后完成的 future(玩具实现)
struct Delay {
completed: Arc<Mutex<bool>>,
waker_stored: Arc<Mutex<Option<Waker>>>,
duration: Duration,
started: bool,
}
impl Delay {
fn new(duration: Duration) -> Self {
Delay {
completed: Arc::new(Mutex::new(false)),
waker_stored: Arc::new(Mutex::new(None)),
duration,
started: false,
}
}
}
impl Future for Delay {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
// 在存储 waker 之前检查是否已经完成
if *self.completed.lock().unwrap() {
return Poll::Ready(());
}
// 存储 waker - executor 可能在每次 poll 时传递一个新的 waker
*self.waker_stored.lock().unwrap() = Some(cx.waker().clone());
// 在第一次 poll 时启动后台定时器
if !self.started {
self.started = true;
let completed = Arc::clone(&self.completed);
let waker = Arc::clone(&self.waker_stored);
let duration = self.duration;
thread::spawn(move || {
thread::sleep(duration);
*completed.lock().unwrap() = true;
// 关键:唤醒 executor 以便它再次 poll 我们
if let Some(w) = waker.lock().unwrap().take() {
w.wake(); // "嘿 executor,我准备好了——再次 poll 我!"
}
});
}
// 在存储 waker 后双重检查完成状态(处理竞态条件)
if *self.completed.lock().unwrap() {
return Poll::Ready(());
}
Poll::Pending // 还未完成
}
}
}
核心洞察:在 C# 中,TaskScheduler 自动处理唤醒。 在 Rust 中,你(或你使用的 I/O 库)负责调用
waker.wake()。 忘记它,你的程序就会静默挂起。
练习:实现 CountdownFuture
🏋️ 练习(点击展开)
挑战:实现一个 CountdownFuture,从 N 倒数到 0,每次被 poll 时打印当前计数。当达到 0 时,返回 Ready("Liftoff!")。
提示:future 需要存储当前计数并在每次 poll 时递减。记住始终重新注册 waker!
🔑 解答
#![allow(unused)]
fn main() {
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct CountdownFuture {
count: u32,
}
impl CountdownFuture {
fn new(start: u32) -> Self {
CountdownFuture { count: start }
}
}
impl Future for CountdownFuture {
type Output = &'static str;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.count == 0 {
println!("Liftoff!");
Poll::Ready("Liftoff!")
} else {
println!("{}...", self.count);
self.count -= 1;
cx.waker().wake_by_ref(); // 立即调度重新 poll
Poll::Pending
}
}
}
}
关键要点:这个 future 每次计数被 poll 一次。每次返回 Pending 时,它立即唤醒自己以便再次被 poll。在生产环境中,你会使用定时器而不是忙 poll。
关键要点——The Future Trait
Future::poll()返回Poll::Ready(value)或Poll::Pending- future 必须在返回
Pending之前注册Waker——executor 用它知道何时重新 pollPin<&mut Self>保证 future 不会在内存中移动(对自引用状态机是必需的——见第 4 章)- Rust 异步中的一切——
async fn、.await、组合器——都构建在这个 trait 之上
另见: 第 3 章 — Poll 工作原理 了解 executor 循环,第 6 章 — 手动构建 Futures 了解更复杂的实现