摘要:Rust 异步编程以 async/await 为语法糖,底层统一抽象为 Future trait(poll 返回 Poll::Ready/ Pending)。await 只能出现在 async 上下文,自动将 async fn 转换为 impl Future。核心机制包括:Context + Waker 主动唤醒(事件驱动无阻塞)、Pin 防止自引用 Future 被移动(编译器生成的匿名 Future 默认 !Unpin)、Unpin 允许普通类型自由移动。async/await 内部被解糖为 generator + yield,配合 Box::pin + 手动 poll 或 Tokio 等执行器实现协作式调度。本文深度解析自引用问题、Pin 安全模型、Waker 注册机制及性能权衡,帮助你彻底掌握“零成本抽象 + 零开销唤醒”的 Rust 异步编程,写出高并发、无线程切换的异步代码。(158 字)

一、async/await 关键字

专业名词释义

  • async:修饰函数、闭包、代码块,返回类型自动变为 impl Future<Output=T>(不立即执行,仅创建可暂停/恢复的执行流)。
  • await:表达式 expr.await,仅能在 async 上下文中使用,左侧必须实现 IntoFutureFuture 自动实现)。await 后类型为 Future::Output

用法示例

async fn f() -> i32 {
    println!("async fn is called.");
    42
}

async fn test() {
    let x: impl Future<Output = i32> = f();
    let r: i32 = x.await;  // 暂停点
    assert_eq!(r, 42);
}

注意事项与最佳实践

  • async fn 调用仅创建 Future,不执行函数体。
  • 深度提示await 代表“暂停/恢复”点,局部变量自动保存到 Future 状态机中。
  • 最佳实践:所有 I/O、定时器、网络操作都封装成 async fn;普通函数仍用同步风格。

二、Future trait

专业名词释义

  • Future:Rust 对“异步执行流”的统一抽象。
  • PollReady(T)(完成) / Pending(需等待)。

定义

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

自定义 Future 示例(Flip:第一次 Pending,第二次 Ready):

struct Flip(bool);

impl Future for Flip {
    type Output = ();
    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
        self.as_mut().0 = !self.0;
        if self.0 { Poll::Pending } else { Poll::Ready(()) }
    }
}

执行 Future(手动轮询):

let mut p = Box::pin(f(5));
loop {
    match p.as_mut().poll(&mut context) {
        Poll::Ready(_) => break,
        Poll::Pending => println!("pending in main"),
    }
}

多个 Future 协作式调度(简单执行器):

let mut tasks: VecDeque<_> = (1..6).map(|c| Box::pin(f(c))).collect();
while let Some(mut p) = tasks.pop_front() {
    match p.as_mut().poll(&mut context) {
        Poll::Ready(_) => continue,
        Poll::Pending => tasks.push_back(p),
    }
}

注意事项与最佳实践

  • 执行器必须循环调用 poll,直到 Ready
  • 深度提示async fn 内部的循环 + await 会生成状态机,自动保存局部变量。
  • 最佳实践:生产环境永远用 Tokio / async-std 执行器;手动 poll 仅用于学习。

三、Task Context 与 Waker

专业名词释义

  • Contextpoll 参数,包装 Waker,用于通知调度器“任务可恢复”。
  • Wakerwake() / wake_by_ref() 主动唤醒任务。

用法(事件注册机制):

static REGISTRY: Mutex<HashMap<EventId, Waker>> = ...;

impl Future for MyFuture {
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
        if need_to_wait_for_event {
            let waker = cx.waker().clone();
            REGISTRY.lock().add(event_id, waker);
            Poll::Pending
        } else {
            Poll::Ready(())
        }
    }
}

注意事项与最佳实践

  • Waker 必须在返回 Pending 前保存,事件触发时调用 wake_by_ref()
  • 深度提示:这是 Rust 异步“零线程切换”的核心——事件驱动而非阻塞线程。
  • 最佳实践:所有 I/O 驱动(Tokio、async-std)都通过此机制实现高效唤醒。

四、async/await 内部原理(解糖)

专业名词释义

  • Generator(生成器)async fn 被编译器转换为 from_generator + yield 状态机。

解糖伪代码

// async fn f() -> T { <body> }
fn f() -> impl Future<Output=T> {
    from_generator(move |_task_context| -> T {
        <body>  // await 被翻译为 poll + yield Pending
    })
}

await 解糖

match operand.into_future() {
    mut pinned => loop {
        let mut pin = unsafe { Pin::new_unchecked(&mut pinned) };
        match pin.as_mut().poll(&mut cx) {
            Poll::Ready(r) => break r,
            Poll::Pending => yield Poll::Pending,
        }
    }
}

注意事项与最佳实践

  • yield 代表“暂停返回 Pending,下次从此处恢复”。
  • 深度提示:这正是自引用问题的根源(状态机内部指针指向自身栈帧)。
  • 最佳实践:理解原理后,99% 场景无需手动写 generator。

五、Pin 类型与自引用问题

专业名词释义

  • 自引用类型:对象内部指针指向自身(struct S { a: T, b: &'a T })。移动后指针变野指针。
  • Pin

    :保证对象不可移动(除非 Unpin),用于保护 async 生成的自引用 Future。

用法

let p = Box::pin(async { ... });  // Pin<Box<impl Future>>
p.as_mut().poll(...);  // 安全

Deref 规则

  • Unpin 类型:可 DerefMut 获取 &mut T
  • !Unpin 类型:只能 Deref 获取 &T(防止移动)。

注意事项与最佳实践

  • async 生成的匿名 Future 默认 !Unpin,必须 Pin
  • 深度提示Drop 时也需小心(swap/replace 可能导致移动,程序员手动保证)。
  • 最佳实践:永远用 Box::pin / tokio::pin!;不要手动实现 !Unpin Future。

六、Unpin trait

专业名词释义

  • Unpin:auto trait,标记“可安全移动”的类型(基础类型 i32String 等均实现;async 生成的 Future 默认 !Unpin)。

Pin 行为

// Unpin 类型
impl<P: Deref<Target: Unpin>> Pin<P> {
    pub fn new(pointer: P) -> Pin<P> { unsafe { Pin::new_unchecked(pointer) } }
}

注意事项与最佳实践

  • !Unpin 无法从 Pin 获取 &mut T(编译器保护)。
  • 深度提示Pin + Unpin 是 Rust 解决自引用 + 移动的终极方案。
  • 最佳实践tokio::pin! 宏自动处理局部变量 Pin;需要移动时用 std::pin::pin!

本章小结 + 进阶练习

学完本章你应该能做到

  • 熟练使用 async/await + Future 编写异步代码
  • 理解 Context/Waker 事件驱动机制
  • 掌握 Pin + Unpin 自引用安全模型
  • 知道 async 内部 generator + yield 原理

进阶练习(建议立刻敲代码):

  1. 实现一个简单的 TimerFuture(用 Waker + 线程 sleep 唤醒)。
  2. VecDeque + Box::pin 写一个协程调度器(支持 100 个并发任务)。
  3. 手动实现一个自引用 Future(!Unpin),验证必须 Pin 才能编译。
  4. tokio::pin! 重写上述 Flip 示例,观察状态机局部变量保留。
  5. 对比 async 函数与手动 Future 实现(性能 + 代码量)。
  6. 结合 Tokio,写一个异步文件读取器(tokio::fs::read + await)。

async/await + Future + Pin + Waker = Rust 零成本异步。掌握自引用保护与主动唤醒机制,你就能写出比线程池更快、更省资源的异步服务!

(完)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐