bigywater.tech
Rust · 108

自定义 Future

真正理解 Future,最好的办法是自己写一个。这篇从零写一个定时器 Future 和一个 join 组合子,用 poll 计数和耗时验证它们的行为,并复现两个最常见的错误:忘记保存 waker 与只保存第一次的 waker。

2 次
一个 100 ms 的自定义 Timer 被 poll 的次数(Pending 一次、Ready 一次)
200 对 100 ms
顺序 await 与 join2 等两个 100 ms 定时器的耗时
永远睡着
没有保存 waker 的 Future 的结局

自己写一个 Timer

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    等 100 ms 的 Timer:实际 100 ms,executor 总共 poll 了 2 次(第一次 Pending,被 wake 后第二次 Ready)

Waker 的细节见Waker 唤醒机制;Pin 见Pin 与 Unpin。

顺序 await 与 join

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    顺序 await 两个 100 ms:总耗时约 200 ms,poll 3 次
    join2 同时等两个 100 ms:总耗时约 100 ms,poll 3 次,结果 (A, B)
    join2 的 150 ms 与 50 ms:总耗时约 160 ms(取最长的),poll 3 次(50 ms 的先完成、150 ms 的后完成)

忘记保存 waker

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    600 ms 过去了,Timer 仍然没有完成:poll 时没保存 waker,到点后 wake 的是 None,executor 永远睡着

waker 会变

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    80 ms 到点后:waker1 被唤醒了吗? false  waker2 被唤醒了吗? true(Timer 每次 poll 都覆盖了保存的 waker,所以只有最新的 waker2 被通知)

分步演示

自定义 Future
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。执行器用 Condvar 实现的最小 block_on
    • 没有验证:Join 子 Future panic 时的行为、join 对 !Unpin 输出的处理、使用 pin-project 的无 unsafe 写法、select 组合子的手写版本、Timer 不用后台线程的实现(需要事件循环)
    京ICP备2026067502号-1京公网安备11010502063487号