Skip to content

异步编程延申

asyncawait

简单来说:async 负责"包装",await 负责"推进".

我们可以从以下两个维度来拆解:

1.async 阶段:静态的代码转换(编译期)

编译器会将 async 代码块转换成一个实现了 Future trait 的状态机(State Machine).

  • 当你定义一个 async fn 时,它不会立即执行任何逻辑.
  • 它仅仅是返回一个保存了该函数执行状态(局部变量、执行进度等)的结构体.

这和 for 循环转 Iterator 非常相似:for 本身只是语法糖,核心是那个能不断产生值的迭代器对象.

2.await 阶段:动态的执行驱动(运行期)

这里是容易产生误解的地方.await 并不是简单的"执行完毕",它是"挂起"与"再唤醒"的控制点.

  • 它不是阻塞:.await 不会像 sleep 那样锁死线程.
  • 它是状态机的边界点:在编译器生成的状态机里,每一个 .await 都是一个 Yield Point(让出点).

推进过程如下:

运行到 .await


调用内部 Future 的 poll()

    ├─── Pending(未就绪)──→ 交出控制权,返回 Executor 等待唤醒

    └─── Ready(已就绪)───→ 获取结果,继续执行后续逻辑

总结

asyncawait
阶段编译期运行期
作用将逻辑打包为 Future 对象驱动状态机向前推进
类比定义迭代器结构调用 .next()

⚠️ 如果没有 .await,Future 就永远只是一个静止的结构体,不会有任何代码被实际执行.

PinUnpin

为什么需要 Pin

在异步状态机中,Future 保存了跨 .await 点的局部变量.问题来了:

rust
async fn example() {
    let s = String::from("hello");
    let r = &s; // r 引用了同一个栈帧内的 s
    some_async_op().await;
    println!("{}", r); // .await 之后还要用 r
}

编译器会把 sr 都放进生成的状态机结构体里.这就产生了自引用结构体——结构体内部有一个字段指向另一个字段.

危险在于:如果这个结构体在内存中被移动(Move),指针 r 还指向旧地址,就变成了悬垂指针!

移动前0x1000              移动后0x2000
┌────────────┐          ┌────────────┐
│ s: "hello" │  →移动→  │ s: "hello" │
│ r: 0x1000  │          │ r: 0x1000  │  ← 指向旧地址!已失效!
└────────────┘          └────────────┘

Pin<P> 的作用就是在编译层面保证:被 Pin 住的值不会再被移动.

而这个保证是通过Rust 类型系统和借用检查器的"禁令"来实现的.简单理解就是被 Pin 包裹的值不能再转移所有权(T)生成新的引用(&mut T) 来移动它.

Unpin:我不怕移动

Unpin 是一个 auto trait,绝大多数类型默认都实现了它.

  • 实现了 Unpin 的类型:可以安全地移动,Pin 对它们没有实际约束效果.
  • 没有实现 Unpin 的类型(如 async 生成的 Future):Pin 会认真保护它们.

可以把 Unpin 理解为一个类型在说:"随便移动我,我内部没有自引用,不怕!"

直观类比

概念类比
Pin把一封信钉在公告板上,它不能再被挪走
Unpin普通便利贴,随便移动,没关系
自引用信里写着"见本信第3行",挪走后"第3行"就找不到了

简单示例

rust
use std::pin::Pin;

// 一个普通的 Unpin 类型,Pin 对它没有实质限制
let mut x = 42i32;
let mut pinned = Pin::new(&mut x);
*pinned = 100; // 完全正常

// async fn 生成的 Future 是 !Unpin 的
// 需要用 Box::pin 或 pin! 宏将其固定在堆/栈上
let fut = async { println!("hello") };
let pinned_fut = Box::pin(fut); // 固定在堆上,可以安全 poll

在实际调用 poll() 时,执行器要求传入 Pin<&mut dyn Future>,正是为了保证在整个轮询过程中 Future 不会被移动.

总结

概念说明
Pin<P>包装指针,承诺不移动其指向的值
Unpin标记trait,表示该类型可以安全移动,Pin不限制它
!Unpin自引用结构(如async状态机)需要被Pin保护
使用场景手写 Future、调用底层 poll 时需要处理 Pin

💡 日常使用 async/await 时,Pin 由编译器和运行时自动处理,通常不需要手动介入.只有在手写 Future 或使用 tokio::pin! 宏固定栈上变量时才会直接接触到它.

基于 MIT 协议发布