Future
代表一个可能尚未完成的计算结果(值)。
-
Future
是什么?(定义)- 它是一个 Trait (接口/协议)。任何实现了
Future
trait 的类型就是一个 Future。 - 它有一个核心方法:
poll
。 - 它的最终目标是产生一个具体的值(比如
String
,i32
, 甚至是()
表示"完成"这个动作本身),或者产生一个错误。
- 它是一个 Trait (接口/协议)。任何实现了
-
Future
的状态:它只有两种可能Pending
(进行中/未就绪) : 这个 Future 代表的任务还没有完成。它还在"烹饪"或"配送"中。Ready(T)
(已完成/已就绪) : 这个 Future 代表的任务已经完成 了,并且成功产出了最终结果T
(比如你点的外卖送到了)。
-
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 在就绪时用来通知执行器"该轮询我了"的回调机制。它解耦了"事件发生"和"执行器检查",避免了忙等待。
- 如果第一次
- 这是
-
async
/.await
:语法糖(让写 Future 像写同步代码一样简单)-
直接手动调用
poll
和传递Waker
非常繁琐且容易出错。 -
async
和.await
是 Rust 提供的强大语法糖。 -
async
: 标记一个代码块(函数或闭包)会返回一个Future
,而不是直接返回值。rustasync fn order_pizza() -> Pizza { // 这个函数返回一个 Future<Pizza>, 而不是 Pizza // ... 模拟下单、等待配送等异步操作 ... }
-
.await
: 用在async
块内部。当你对一个 Future 使用.await
时:- 编译器会自动生成代码去
poll
这个 Future。 - 如果
poll
返回Pending
,当前这个async
块/函数会在这里"暂停"并立即返回一个Pending
状态的 Future 给它的调用者。 - 神奇之处: 当底层那个被等待的 Future 就绪(通过它的
Waker
通知了执行器),执行器会重新唤醒(resume)这个暂停的async
函数 ,代码会从上次.await
暂停的地方继续往下执行 ,并且拿到Ready
的结果值。 - 效果: 代码看起来像是同步顺序执行的("下单 -> 等待 -> 吃"),但底层是非阻塞的异步操作 ,由编译器和执行器处理了所有复杂的
poll
和Waker
逻辑。
rustasync fn have_dinner() { let pizza = order_pizza().await; // 看起来像同步等待,实际是非阻塞暂停 eat(pizza); }
- 编译器会自动生成代码去
-
-
执行器(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
,执行器就完成了该任务(或处理其结果)。
- 它持有一个或多个待处理的 Future(通常是顶层的
- 关键点: 执行器高效地管理着大量可能阻塞(IO)的 Future,让它们在 IO 等待时让出 CPU 去执行其他就绪的 Future,最大化利用 CPU 资源。这是异步编程高性能的基础。
- Future 本身是惰性(Lazy) 的。仅仅定义一个
如果你熟悉 JavaScript 的 promises,有些东西可能会觉得奇怪。在 JavaScript 中,promises 是在事件循环中被执行,并且没有其他的可以运行它们的选择。executor
函数是立即运行的。但是,从本质上来讲,promise 仍然只是简单地定义了一系列将来要执行的指令。在 Rust 中,executor 可以选择许多异步策略中的任意一个来运行。
总结与关键点:
Future
是一个 Trait: 代表一个异步计算 ,最终产出值 (Ready(T)
) 或失败。- 状态机: 每个 Future 内部像一个状态机,只有
Pending
或Ready
状态。 poll
驱动: 通过调用poll
来检查/推进 Future 的状态。poll
必须快速返回(非阻塞)。Waker
通知: 是poll
参数的一部分。Future 在就绪时用它通知执行器"我好了,快再来poll
我!"。这是实现高效等待的核心。async
/.await
: 语法糖! 让你用看似同步 的代码结构编写真正的异步逻辑 。编译器将其转换为状态机和poll
/Waker
调用。- 执行器(Executor): 必不可少的引擎! 负责调度和驱动(不断
poll
)所有的 Future。标准库只定义了Future
trait,不提供执行器,你需要用第三方库(如 Tokio, async-std)。 - 惰性: Future 在被执行器
poll
之前,什么也不做。
具体流程:
- 你 (
async fn have_dinner()
) 决定点外卖 (order_pizza().await
)。 - 调用
order_pizza()
返回一张订单收据(Future),不代表披萨到手了。 - 你把这张收据交给外卖调度中心(Executor)。
- 调度中心 (
poll
) 立刻打电话(poll
) 给餐厅问:"披萨好了吗?"。 - 情况A (Ready) :餐厅说"好了!",调度中心立刻把披萨 (
Pizza
) 送给你,你开始吃 (eat(pizza)
)。 - 情况B (Pending) :餐厅说"还在做"。调度中心说:"做好后立刻打这个电话 (
Waker
) 通知我! ",然后就把你(have_dinner
这个 Future)暂时挂起,转头去处理其他人的订单(其他 Future)了。 - 餐厅做好披萨,立刻拨打调度中心留下的电话 (
waker.wake()
) 通知。 - 调度中心收到通知,知道你的订单有进展了,立刻再次打电话 (
poll
) 给餐厅。 - 这次餐厅肯定说"好了!" (
Ready(Pizza)
),调度中心把披萨送给你,你继续执行"吃"的动作。
async
/.await
让你只需要写 "点披萨 -> 等披萨 -> 吃披萨" 这样顺序的代码,而调度中心(执行器)和电话通知机制 (Waker
) 在底层自动处理了所有复杂的等待和切换工作。Rust 的 Future 核心就是定义了这个"订单收据"(Future)的接口 (poll
+ Waker
),以及如何与调度中心交互的协议。