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()

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

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 协议发布