Rust - Futures

Future 代表一个可能尚未完成的计算结果(值)。

  1. Future 是什么?(定义)

    • 它是一个 Trait (接口/协议)。任何实现了 Future trait 的类型就是一个 Future。
    • 它有一个核心方法:poll
    • 它的最终目标是产生一个具体的值(比如 String, i32, 甚至是 () 表示"完成"这个动作本身),或者产生一个错误。
  2. Future 的状态:它只有两种可能

    • Pending (进行中/未就绪) : 这个 Future 代表的任务还没有完成。它还在"烹饪"或"配送"中。
    • Ready(T) (已完成/已就绪) : 这个 Future 代表的任务已经完成 了,并且成功产出了最终结果 T(比如你点的外卖送到了)。
  3. poll 方法:检查进度

    • 这是 Future trait 的核心方法:fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T>
    • 浅显理解 poll
      • 想象你每隔几分钟就打电话问外卖平台 :"我的外卖到了吗?" (poll 就是那个"打电话问"的动作)。
      • 返回值 Poll<T> 是一个枚举:
        • Poll::Pending: 平台告诉你:"还没到呢,还在路上。"(Future 未完成)。
        • Poll::Ready(T): 平台告诉你:"到了!您的外卖是 T(比如一份披萨)。"(Future 完成,返回结果 T)。
    • 关键点 1:非阻塞 - 当你调用 poll 时:
      • 如果结果是 Ready(T),你立刻拿到结果,可以吃了。
      • 如果结果是 Pending,函数立刻返回它不会卡在那里傻等! 你(调用 poll 的代码)可以马上去做别的事情(处理其他 Future 或者计算)。这就是 异步(非阻塞) 的核心。
    • 关键点 2:谁负责再次 poll?(Waker 的作用)
      • 如果第一次 poll 返回 Pending,我们怎么知道什么时候 该再打一次电话 (poll) 问进度呢?总不能无限循环打吧?效率太低!
      • 这就是 Context 参数(特别是它里面的 Waker)的魔法所在。
      • 当你第一次 poll 一个未完成的 Future 时,你把一个"闹钟" (Waker) 交给它 (比如告诉外卖平台:"如果外卖到了,请立刻打电话通知我")。
      • 当 Future 内部的状态发生变化 (比如,后台 IO 操作完成、定时器到期、锁可用),并且它准备好变成 Ready 状态时 ,它就会用这个之前保存的 Waker "打电话通知你" (实际上是调用 waker.wake() 方法)。
      • 这个"通知"就是告诉 "调度器/执行器"(Executor) :"嘿!我之前返回 Pending 的那个 Future 现在可能有进展了(很可能就绪了),你赶紧再来 poll 我一次看看!"
      • 执行器收到这个通知 (wake 调用),就会在合适的时机 (可能立刻,也可能稍后排队)再次调用这个 Future 的 poll 方法。这次调用,很可能就返回 Ready(T) 了。
      • 总结 Waker 它是 Future 在就绪时用来通知执行器"该轮询我了"的回调机制。它解耦了"事件发生"和"执行器检查",避免了忙等待。
  4. async/.await:语法糖(让写 Future 像写同步代码一样简单)

    • 直接手动调用 poll 和传递 Waker 非常繁琐且容易出错。

    • async.await 是 Rust 提供的强大语法糖。

    • async 标记一个代码块(函数或闭包)会返回一个 Future ,而不是直接返回值。

      rust 复制代码
      async fn order_pizza() -> Pizza { // 这个函数返回一个 Future<Pizza>, 而不是 Pizza
          // ... 模拟下单、等待配送等异步操作 ...
      }
    • .await 用在 async 块内部。当你对一个 Future 使用 .await 时:

      • 编译器会自动生成代码去 poll 这个 Future。
      • 如果 poll 返回 Pending当前这个 async 块/函数会在这里"暂停"并立即返回一个 Pending 状态的 Future 给它的调用者
      • 神奇之处: 当底层那个被等待的 Future 就绪(通过它的 Waker 通知了执行器),执行器会重新唤醒(resume)这个暂停的 async 函数 ,代码会从上次 .await 暂停的地方继续往下执行 ,并且拿到 Ready 的结果值。
      • 效果: 代码看起来像是同步顺序执行的("下单 -> 等待 -> 吃"),但底层是非阻塞的异步操作 ,由编译器和执行器处理了所有复杂的 pollWaker 逻辑。
      rust 复制代码
      async fn have_dinner() {
          let pizza = order_pizza().await; // 看起来像同步等待,实际是非阻塞暂停
          eat(pizza);
      }
  5. 执行器(Executor):驱动引擎

    • Future 本身是惰性(Lazy) 的。仅仅定义一个 async 函数或者创建一个 Future 不会做任何事 。它需要被驱动(Driven)
    • 执行器(如 tokio::runtime, async-std 就是干这个的:
      • 它持有一个或多个待处理的 Future(通常是顶层的 async main 函数或 spawn 的任务)。
      • 它不断轮询(poll 这些 Future(通常在一个循环中)。
      • 当某个 Future 返回 Pending 时,执行器就把它挂起(但记录下它关联的 Waker)。
      • 当某个 Waker 被调用(wake)时,执行器就知道对应的 Future 可能有进展了,于是将它重新加入队列,在后续的轮询中再次 poll 它。
      • 当 Future 返回 Ready,执行器就完成了该任务(或处理其结果)。
    • 关键点: 执行器高效地管理着大量可能阻塞(IO)的 Future,让它们在 IO 等待时让出 CPU 去执行其他就绪的 Future,最大化利用 CPU 资源。这是异步编程高性能的基础。

如果你熟悉 JavaScript 的 promises,有些东西可能会觉得奇怪。在 JavaScript 中,promises 是在事件循环中被执行,并且没有其他的可以运行它们的选择。executor函数是立即运行的。但是,从本质上来讲,promise 仍然只是简单地定义了一系列将来要执行的指令。在 Rust 中,executor 可以选择许多异步策略中的任意一个来运行。

总结与关键点:

  1. Future 是一个 Trait: 代表一个异步计算 ,最终产出值 (Ready(T)) 或失败。
  2. 状态机: 每个 Future 内部像一个状态机,只有 PendingReady 状态。
  3. poll 驱动: 通过调用 poll 来检查/推进 Future 的状态。poll 必须快速返回(非阻塞)
  4. Waker 通知:poll 参数的一部分。Future 在就绪时用它通知执行器"我好了,快再来 poll 我!"。这是实现高效等待的核心。
  5. async/.await 语法糖! 让你用看似同步 的代码结构编写真正的异步逻辑 。编译器将其转换为状态机和 poll/Waker 调用。
  6. 执行器(Executor): 必不可少的引擎! 负责调度和驱动(不断 poll)所有的 Future。标准库只定义了 Future trait,不提供执行器,你需要用第三方库(如 Tokio, async-std)。
  7. 惰性: Future 在被执行器 poll 之前,什么也不做。

具体流程:

  1. 你 (async fn have_dinner()) 决定点外卖 (order_pizza().await)。
  2. 调用 order_pizza() 返回一张订单收据(Future),不代表披萨到手了。
  3. 你把这张收据交给外卖调度中心(Executor)
  4. 调度中心 (poll) 立刻打电话(poll 给餐厅问:"披萨好了吗?"。
  5. 情况A (Ready) :餐厅说"好了!",调度中心立刻把披萨 (Pizza) 送给你,你开始吃 (eat(pizza))。
  6. 情况B (Pending) :餐厅说"还在做"。调度中心说:"做好后立刻打这个电话 (Waker) 通知我! ",然后就把你(have_dinner 这个 Future)暂时挂起,转头去处理其他人的订单(其他 Future)了。
  7. 餐厅做好披萨,立刻拨打调度中心留下的电话 (waker.wake()) 通知。
  8. 调度中心收到通知,知道你的订单有进展了,立刻再次打电话 (poll) 给餐厅
  9. 这次餐厅肯定说"好了!" (Ready(Pizza)),调度中心把披萨送给你,你继续执行"吃"的动作。

async/.await 让你只需要写 "点披萨 -> 等披萨 -> 吃披萨" 这样顺序的代码,而调度中心(执行器)和电话通知机制 (Waker) 在底层自动处理了所有复杂的等待和切换工作。Rust 的 Future 核心就是定义了这个"订单收据"(Future)的接口 (poll + Waker),以及如何与调度中心交互的协议。

相关推荐
1nv1s1ble8 小时前
记录rust滥用lazy_static导致的一个bug
算法·rust·bug
华科云商xiao徐14 小时前
用Rust如何构建高性能爬虫
爬虫·rust
景天科技苑15 小时前
【Rust UDP编程】rust udp编程方法解析与应用实战
开发语言·rust·udp·udp编程·rust udp
火柴就是我1 天前
Rust学习之 所有权理解
rust
火柴就是我1 天前
Rust 学习之变量的可变与不可变
rust
Pomelo_刘金2 天前
Rust: 1.86.0 新版本发布。
rust
Pomelo_刘金2 天前
发布 Rust 1.87.0 —— 以及 Rust 十周年!
rust
susnm2 天前
你的第一个组件
rust·全栈
该用户已不存在2 天前
OpenAI 用 Rust 重写 AI 工具,你的本地开发环境跟上了吗?
前端·javascript·rust