本文目录
HTTP 客户端把请求发出去之后,字节还没进内存——这一刻程序需要一种「占位」:将来 要么拿到响应体,要么拿到超时异常。同步代码里你 阻塞 在 recv 上;asyncio 里则往往先拿到 Future,在 pending 阶段继续 调度 别的 Task,等生产者 set_result 后再恢复等待方。把 Future 当成状态机读,而不是当成「某个类的实例」,后面 Task、gather、取消都会顺很多。
Future 是什么:一次结果的信箱
Future 不是协程,也不是线程。它是 事件循环 体系里的 状态容器,表示 恰好一次 的结果承诺:
| 状态 | 含义 |
|---|---|
| pending | 结果尚未写入,可 add_done_callback,可 cancel() |
| cancelled | 被 cancel(),await 时抛 CancelledError |
| done | 已有 结果 或 异常,不可再改 |
import asyncio
async def demo_future() -> None:
fut: asyncio.Future[int] = asyncio.get_running_loop().create_future()
print(fut.done(), fut.cancelled()) # False False
fut.set_result(42)
print(fut.done(), fut.result()) # True 42
asyncio.run(demo_future())create_future() 得到空信箱;谁 持有 Future 引用,谁 在 I/O 完成或计算结束时调用 set_result / set_exception。消费方用 await fut 或 fut.add_done_callback 等待落定。
若对 已 done 的 Future 再 set_result,会 InvalidStateError——状态机只允许 pending → done 一次。这个约束保证:多个等待者 读到的要么是同一份结果,要么是同一份异常,不会「后写覆盖先写」。
pending → done:结果与异常两条路
完成只有两种合法落盘:
import asyncio
async def ok_and_err() -> None:
ok: asyncio.Future[str] = asyncio.get_running_loop().create_future()
bad: asyncio.Future[str] = asyncio.get_running_loop().create_future()
ok.set_result("hello")
bad.set_exception(ValueError("boom"))
print(await ok) # hello
try:
await bad
except ValueError as e:
print(repr(e)) # ValueError('boom')
asyncio.run(ok_and_err())await 在 done 且为异常时 重新抛出 该异常;在 pending 时 挂起当前协程,直到 Future 变 done。挂起期间 事件循环 可以跑别的 Task——这是 async 模型与「线程阻塞在 future.result()」的根本差异。
fut.result() 与 fut.exception() 是 同步 取值 API:在 pending 时不能调 result()(会报错);在 done 且为异常时 result() 会把异常 再抛一次。协程代码里更常用 await,由 loop 负责挂起/恢复。
重复 set_exception 或混用 set_result / set_exception 同样非法。cancel() 只在 pending 时有效:
import asyncio
async def cancel_demo() -> None:
fut: asyncio.Future[None] = asyncio.get_running_loop().create_future()
async def waiter() -> None:
try:
await fut
except asyncio.CancelledError:
print("waiter: cancelled on fut")
t = asyncio.create_task(waiter())
await asyncio.sleep(0)
fut.cancel()
await t
asyncio.run(cancel_demo())
# waiter: cancelled on fut注意:这里 cancel 的是 Future,await 它的 Task 会以 CancelledError 结束。若 Future 先被 set_result,cancel() 无效——done 状态不可逆转。
谁 set_result:库、回调、协程
三类常见 生产者:
- 底层 transport / protocol:socket 可读,protocol 读完后
future.set_result(data)。 loop.call_soon链:同步步骤完成,在 callback 里写结果。- Task 驱动协程:协程
return或抛错,Task 内部等价于set_result/set_exception。
模拟「异步读一行」——生产者不在协程里,而在 I/O 完成回调:
import asyncio
from typing import Callable
def fake_read_line(on_done: Callable[[str], None]) -> None:
on_done("line-1\n")
async def read_line_async() -> str:
loop = asyncio.get_running_loop()
fut: asyncio.Future[str] = loop.create_future()
def _finish(line: str) -> None:
if not fut.done():
fut.set_result(line)
loop.call_soon(lambda: fake_read_line(_finish))
return await fut
async def main() -> None:
line = await read_line_async()
print(repr(line)) # 'line-1\n'
asyncio.run(main())真实库里你很少手写 create_future,但 语义相同:注册 I/O → 完成时 写 Future → 等待方 await Future。asyncio.sleep 内部也是 Future + 定时器;create_task 则是 Task 型 Future + 协程推进器。
多个等待者:同一个 Future
一个 Future 可被多个协程 await(或多次 add_done_callback)。set_result 一次,所有等待者一起被唤醒,拿到相同结果:
import asyncio
async def shared_wait(tag: str, fut: asyncio.Future[int]) -> None:
v = await fut
print(f"{tag} got {v}")
async def main() -> None:
fut: asyncio.Future[int] = asyncio.get_running_loop().create_future()
asyncio.create_task(shared_wait("A", fut))
asyncio.create_task(shared_wait("B", fut))
await asyncio.sleep(0)
fut.set_result(100)
await asyncio.sleep(0)
asyncio.run(main())
# A got 100
# B got 100这像广播完成信号,而不是每个消费者各有一个信箱。设计 API 时要想清楚:你需要的是「共享一次结果」还是「每个调用独立 Future」。
add_done_callback:回调何时跑
Future.add_done_callback(fn) 约定:fn(fut) 在 变为 done 的同一轮 loop 里、通常 稍后 执行——在 set_result 那条调用栈 unwind 之后,由 事件循环 排进 就绪队列。
import asyncio
async def callback_timing() -> None:
fut: asyncio.Future[int] = asyncio.get_running_loop().create_future()
log: list[str] = []
def on_done(f: asyncio.Future[int]) -> None:
log.append(f"cb result={f.result()}")
fut.add_done_callback(on_done)
log.append("before set")
fut.set_result(7)
log.append("after set")
await asyncio.sleep(0)
print(log)
asyncio.run(callback_timing())
# ['before set', 'after set', 'cb result=7']after set 先于 cb——callback 不是 在 set_result 内部同步跑。若在 callback 里写慢逻辑,会 阻塞 调度;若需要 await,应 asyncio.create_task 包一层协程。
在 已 done 的 Future 上注册 callback,会 立即 调度一次——便于「晚到的监听者」不会漏事件。
链式 callback 与错误传播
callback 里若再 set_result 到另一个 Future,可以手工拼管道;更常见的是协程里 await 链。callback 若 抛异常,默认只打印到 stderr(3.8+ 行为有过调整),不会 自动传给原 Task——这是 callback 与 await 的重要区别:await 把异常接进协程栈;裸 callback 要自己 try/except。
import asyncio
async def watch() -> None:
fut: asyncio.Future[int] = asyncio.get_running_loop().create_future()
def boom(_: asyncio.Future[int]) -> None:
raise RuntimeError("in callback")
fut.add_done_callback(boom)
fut.set_result(1)
await asyncio.sleep(0) # 给 loop 跑 callback 的机会
asyncio.run(watch())
# RuntimeError: in callback (具体表现依版本与 loop 配置)生产代码里:短 callback 做记账/唤醒;复杂逻辑放回 Task。
Task:挂着协程的 Future
asyncio.Task 继承 Future,多绑一个 协程对象。创建 Task 后,loop 驱动 协程直到 return 或未捕获异常,然后把返回值/异常写入 Future 状态。
import asyncio
async def compute(n: int) -> int:
await asyncio.sleep(0.01)
return n * n
async def main() -> None:
task: asyncio.Task[int] = asyncio.create_task(compute(6))
print(isinstance(task, asyncio.Future)) # True
print(task.done()) # False
v = await task
print(v, task.done()) # 36 True
asyncio.run(main())直觉:Future = 「结果坑位」;Task = 「坑位 + 自动跑协程填坑」。create_task(coro) 造 Task 并 schedule 第一步。协程 return 36 在实现层等价于对该 Task set_result(36)。
协程 不 创建 Task 就不会自己跑:
import asyncio
async def idle() -> str:
return "x"
coro = idle()
print(asyncio.iscoroutine(coro)) # True
coro.close()Task 与裸 Future 的另一个区别:Task 可被 cancel() 注入 CancelledError 打断协程;裸 Future 通常由外部 I/O 完成 set_result,取消语义更简单。
Task 名、例外与 exception()
Task 支持 get_name() / set_name(),便于日志里区分并发单元:
import asyncio
async def fail() -> None:
raise KeyError("missing")
async def main() -> None:
t = asyncio.create_task(fail(), name="loader")
try:
await t
except KeyError:
print(t.name(), t.done(), t.exception()) # loader True KeyError(...)
asyncio.run(main())t.exception() 在 done 且失败时返回异常对象;成功时返回 None。这与 await 再 try/except 等价,适合「已经 done、只想探测」的 调度 代码。
Future 与 concurrent.futures 的同名类
标准库还有 concurrent.futures.Future(线程/进程池)。名字一样,类型不同:
import asyncio
from concurrent.futures import ThreadPoolExecutor
async def bridge() -> int:
loop = asyncio.get_running_loop()
with ThreadPoolExecutor() as pool:
af = loop.run_in_executor(pool, pow, 2, 8)
return await af
print(asyncio.run(bridge())) # 256run_in_executor 返回 asyncio.Future,内部桥接线程池里的 concurrent.futures.Future。不能 await 线程池 Future 本身——类型不对,loop 不知道何时挂起协程。
与上一章:Future 在 loop 里的位置
回顾 事件循环 的一轮:某 Task await 一个 pending 的 Future → 该 Task 移出 就绪队列 → I/O 或定时器完成 → 生产者 set_result → Future 的 done callback 与等待 Task 一起进 就绪队列 → loop 调度 恢复。整条链的枢纽就是 Future 状态从 pending 到 done 的那一次跃迁。
常见误解
误解一:「await 会阻塞线程。」 await 挂起协程,线程仍可 调度 其他 Task。
误解二:「Future 就是 Promise,和 JS 一模一样。」 概念相近,但 Python 的 Future 与 Task 拆分、与 loop 绑定更紧。
误解三:「Task.cancel() 立刻停协程。」 cancel 在 下一个 await 才注入 CancelledError;finally 仍会跑。
误解四:「done_callback 里可以写阻塞 I/O。」 会冻住 事件循环;应 create_task 或线程池。
误解五:「set_result 可以从任意线程调。」 应对 loop 线程 call_soon_threadsafe 包装 set_result,否则数据竞争。
ensure_future 与 asyncio.wrap_future
库代码里常见 asyncio.ensure_future(obj):若 obj 已是 Task/Future 则原样返回;若是 协程 则 create_task。这是「我不知道调用方给的是 coroutine 还是 Future,但我要 调度 它」的入口。
import asyncio
async def coro() -> int:
return 1
async def main() -> None:
f1 = asyncio.ensure_future(coro())
f2 = asyncio.ensure_future(f1)
print(f1 is f2) # True
print(await f2) # 1
asyncio.run(main())asyncio.wrap_future 把 concurrent.futures.Future 包成 asyncio.Future,与 run_in_executor 内部路径类似。自己接第三方线程池时常用到。
逐步推演:从 await 到 set_result
把上一章 事件循环 与 Future 连起来读一遍(单 Task 等手工 Future):
import asyncio
async def waiter() -> int:
loop = asyncio.get_running_loop()
fut: asyncio.Future[int] = loop.create_future()
async def producer() -> None:
await asyncio.sleep(0.05)
fut.set_result(200)
asyncio.create_task(producer())
print("waiter: before await")
v = await fut
print("waiter: after await", v)
return v
asyncio.run(waiter())文字时间线:
- waiter 创建 pending 的 Future,启动 producer Task。
- waiter 打印
before await,执行await fut→ 协程 暂停,Future 登记等待者。 - 事件循环 调度 producer → producer
await sleep→ producer 暂停,loop poll 定时器。 - 0.05s 后 producer 恢复,
fut.set_result(200)→ Future done → waiter 进 就绪队列。 - waiter 恢复,打印
after await 200。
全程单线程,没有 OS 级 阻塞 在 await 上;调度 在步骤 2–5 间穿插 producer 与 waiter 的 暂停/恢复。
手工拼 Future:双阶段 I/O
再看一个稍长的 pending 阶段:先连接,再读 body——两阶段各一个 Future,由同一 协程 顺序 await:
import asyncio
from typing import Callable
def connect(host: str, on_connected: Callable[[], None]) -> None:
on_connected()
def read_body(on_data: Callable[[bytes], None]) -> None:
on_data(b"OK")
async def fetch() -> bytes:
loop = asyncio.get_running_loop()
conn_fut: asyncio.Future[None] = loop.create_future()
body_fut: asyncio.Future[bytes] = loop.create_future()
def on_conn() -> None:
if not conn_fut.done():
conn_fut.set_result(None)
def on_body(data: bytes) -> None:
if not body_fut.done():
body_fut.set_result(data)
loop.call_soon(lambda: connect("example", on_conn))
await conn_fut
loop.call_soon(lambda: read_body(on_body))
return await body_fut
async def main() -> None:
print(await fetch())
asyncio.run(main())
# b'OK'第一阶段 await conn_fut 时 协程 暂停;on_conn set_result 后恢复,再进入第二阶段。真实 HTTP 客户端把 socket 读就绪 与 解析 拆成更多 Future,但状态跃迁仍是 pending → done。
Task 与 Future 的 cancel 差异(再强调)
对 Task cancel():向 协程 注入 CancelledError。对裸 Future cancel():仅标记 cancelled,没有 协程 帧要清理。若 Future 已 await 于 Task,cancel Future 会让 Task 以 CancelledError 结束。混用 cancel 目标时 trace 容易乱——优先 cancel Task(用户可见的 调度 单元)。
Future 状态机(文字版)
create_future()
│
▼
pending ──cancel()──► cancelled
│
├── set_result(v) ──► done (result=v)
│
└── set_exception(e) ──► done (exception=e)pending 时可 add_done_callback、可 cancel()。done 后 result() / exception() 只读;await 在 pending 挂起 协程,在 done 取值或抛异常。Task 继承这套状态,额外多 协程 驱动器自动 set_result。
两个 Task 等同一 Future(竞态直觉)
import asyncio
async def waiter(tag: str, fut: asyncio.Future[int]) -> None:
print(f"{tag} waiting")
print(f"{tag} got", await fut)
async def main() -> None:
fut: asyncio.Future[int] = asyncio.get_running_loop().create_future()
asyncio.create_task(waiter("X", fut))
asyncio.create_task(waiter("Y", fut))
await asyncio.sleep(0)
print("setting result")
fut.set_result(88)
await asyncio.sleep(0)
asyncio.run(main())**set_result** 一次,X、Y 都拿到 88。pending 阶段登记的所有等待者一起被唤醒——不会出现「第二个 await 永远等不到」除非 never set_result。
为何 callback 不能 await
done_callback 在 loop 线程同步执行(从 ready 队列角度)。若 callback 里 await,当前并不是 协程 帧——语法也不允许。模式:callback 里 loop.create_task(coro()) 启动新 协程 去 await 慢路径。
Task 创建即 schedule:与裸 coroutine 对比
import asyncio
async def quick() -> None:
print("quick runs")
async def main() -> None:
c = quick()
print("coroutine only, not scheduled yet")
t = asyncio.create_task(quick())
await asyncio.sleep(0)
c.close()
asyncio.run(main())第一个 quick() 不打印 quick runs;create_task(quick()) 会。Task = Future + 「我已经在 事件循环 排队了」。
同步等待 vs await Future:线程视角对照
在线程池代码里,你 submit(fn) 拿到 concurrent.futures.Future,在 f.result() 上 阻塞 直到 done。asyncio 里 await asyncio.Future 挂起 协程,事件循环 线程去跑别的 Task——这是 async 高并发的支点。不要把 await fut 想成「睡直到有结果」;应想成「把本 协程 登记在 fut 的等待列表上,把 控制权 交还 调度」。
result()、exception() 与 await 的分工
fut.result() 是同步 API:pending 调会错;done 且异常时会 再抛。在 async 函数里优先 await fut,由 loop 负责 挂起/恢复。在 同步 callback(add_done_callback)里不能 await,只能 fut.result() 或 fut.exception(),且应快速返回。
import asyncio
async def sync_style_bad() -> None:
fut: asyncio.Future[int] = asyncio.get_running_loop().create_future()
asyncio.get_running_loop().call_later(0.05, fut.set_result, 9)
# fut.result() # 在 async 里阻塞 loop,错误
print(await fut)
asyncio.run(sync_style_bad())
# 9延迟 set_result 与取消 race
pending 期间 cancel() 与 set_result 竞态:cancel 成功后 set_result 抛 InvalidStateError;若 set_result 先完成,cancel 无效。写库时应用 if not fut.done(): fut.set_result(...) 模式(见前文 fake_read_line)。
import asyncio
async def race() -> None:
fut: asyncio.Future[int] = asyncio.get_running_loop().create_future()
fut.cancel()
try:
fut.set_result(1)
except asyncio.InvalidStateError:
print("lost race to cancel")
asyncio.run(race())
# lost race to cancelTask 的 get_coro 与调试
task.get_coro()(3.8+)取绑定的 协程 对象,配合栈追踪看 Task 卡在哪个 await。生产日志里打印 task.get_name()、repr(task) 比打印裸 Future 更可读——Task 是运维与 调度 的常用粒度。
Future 链:callback 触发下一级 I/O
复杂客户端常级联:连接 Future done → callback 发起 读 Future → 读 done set_result 给上层 await。每一跳都是 pending → done;事件循环 在跳与跳之间 调度 其它 Task。理解 Future 链后,读 aiohttp / httpx 源码不会迷失在 callback 海里。
再次对照 Task
| 裸 Future | Task | |
|---|---|---|
| 谁推进 pending | 外部 set_result | loop 跑 协程 自动 set |
| 典型来源 | create_future、I/O 层 | create_task(coro) |
| cancel 语义 | 标记 cancelled | 标记 + 向 协程 注入 CancelledError |
await | 等同 Future | 等同 Future(继承) |
Task 是 Future;不是所有 Future 都是 Task。
包装线程结果:wrap_future 走读
import asyncio
from concurrent.futures import ThreadPoolExecutor
async def main() -> None:
loop = asyncio.get_running_loop()
with ThreadPoolExecutor() as ex:
cf = ex.submit(pow, 2, 16)
af = asyncio.wrap_future(cf)
print(await af)
asyncio.run(main())
# 65536wrap_future 监听线程池 Future done,在 loop 线程 set_result 到 asyncio.Future——与 run_in_executor 同类桥接。
await 与 Future 在字节码层的协作(概念)
await fut 编译为:检查 fut 是否已 done → 若 pending,挂起当前 协程 并把 fut 登记为 awaitable → 事件循环 在 fut.set_result 时恢复 协程。因此 set_result 的调用线程必须是 loop 线程,或通过 call_soon_threadsafe 投递——否则 等待者 与 生产者 数据竞争。
多个 Future 顺序 await:仍无并发
import asyncio
async def one() -> asyncio.Future[int]:
f = asyncio.get_running_loop().create_future()
asyncio.get_running_loop().call_later(0.05, f.set_result, 1)
return f
async def main() -> None:
f1 = await one()
f2 = await one()
print(await f1, await f2)
asyncio.run(main())**await one()** 先等第一个 Future 完成再调第二个 one()——总耗时约 0.1s。并发要 create_task(one()) 两次再 gather。
Task.exception() 与日志
Task done 后 task.exception() 取异常而不 re-raise,适合 add_done_callback 里记录失败:
import asyncio
async def fail() -> None:
raise RuntimeError("x")
def log_task(t: asyncio.Task[None]) -> None:
if t.cancelled():
return
exc = t.exception()
if exc:
print("logged", exc)
async def main() -> None:
t = asyncio.create_task(fail())
t.add_done_callback(log_task)
try:
await t
except RuntimeError:
pass
asyncio.run(main())
# logged xFuture 不是缓存容器
Future 表示 一次 结果。done 后不可 set_result again。若需要多次推送,用 Queue、async generator 或 回调流——不要复用同一个 Future。
与 事件循环 就绪队列的最后一环
set_result → Future 标记 done → 等待该 Future 的 Task 被 loop call_soon 进 就绪队列 → 下一轮 调度 恢复 协程。记牢这条链,pending 卡住时问:谁该 set_result?是否已 call_soon?
重复读取 done 的 Future
await fut 多次、或 fut.result() 多次,在 done 且成功时返回 同一结果——Future 像 单次广播。异常路径 await 每次 re-raise 同一异常对象(依实现可能新建 traceback)。
asyncio.gather 与 Future 列表
gather 返回列表,元素是各 awaitable 的结果,不是 Future 本身——除非某元素本身就是 Future 且 return_exceptions 等。理解 gather 输出与 Task 对象的区别,避免 **gather 完再 await task 双重等待。
超时 Future:wait_for 实现直觉
wait_for 内部 ensure_future(aw),并注册 TimerHandle;超时 cancel 底层 Task,await 方捕 TimeoutError。底层 Task 若 吞 cancel,wait_for 仍返回超时,但 Task 可能仍在跑——须 await 或显式 cancel 确认。
手写 Future 的最小模式(模板)
import asyncio
from collections.abc import Callable
def run_async(fn: Callable[[asyncio.Future], None]) -> asyncio.Future:
loop = asyncio.get_running_loop()
fut = loop.create_future()
def _safe_set(f: asyncio.Future, result=None, exc=None):
if f.done():
return
if exc:
f.set_exception(exc)
else:
f.set_result(result)
try:
fn(fut)
except Exception as e:
_safe_set(fut, exc=e)
return fut库作者用类似模式把 callback 风格 API 包成 await 友好 Future。
Task 命名与可观测性
生产环境给 Task set_name(f"http-{host}"),日志 asyncio.all_tasks() 列出 pending Task,配合 Future done 统计,能回答「请求 hung 在哪个 await」——Future 状态机是观测 hung 的锚点。
与 协程 章:await 谁
协程 await fut:fut pending 则 协程 暂停;fut done 则取值。Task 是 自驱动 Future(协程 return → set_result)。读 Task 栈 + Future done 位,定位 pending 链。
深入:Future 在 asyncio 模块中的位置
标准库把 Future 放在 asyncio 包核心:Task 继承 Future;gather、wait、wait_for 都操作 Awaitable,底层大量 ensure_future。第三方库(aiohttp、httpx)在 protocol 层 create_future 或 loop.create_task,对上层仍暴露 await response.read()。读任何 async 库,找 谁持有 Future、谁 set_result,比记类名列表有用。
异常链与 Future
set_exception(e) 保存 e;await 时 re-raise 同一对象(__context__ 链保留若 raise ... from)。Task 未捕获异常 → Task done with exception → await task 抛错。**gather(return_exceptions=True)** 把异常 对象 放进列表,不抛——适合批处理报表。
并发 set_result:线程安全
仅 loop 线程应 直接 fut.set_result。其他线程须:
import asyncio
import threading
async def main() -> None:
loop = asyncio.get_running_loop()
fut = loop.create_future()
def worker():
loop.call_soon_threadsafe(fut.set_result, "from-thread")
threading.Thread(target=worker).start()
print(await fut)
asyncio.run(main())
# from-threadcall_soon_threadsafe 保证 set_result 在 loop 线程执行,与 await 方 无 race。
长文走读:Task 完成时发生了什么
- 协程 执行
return 42或抛 未捕获异常。 - Task 内部捕获 StopIteration(42) 或异常。
- Task 调用
Future.set_result(42)或set_exception。 - Future 标记 done,触发 add_done_callback 队列(排进 ready)。
- await task 的 协程 被 call_soon 恢复,await 表达式求值为 42 或 抛异常。
五步都在 同一 loop 线程 完成(除 threadsafe 路径)。pending 卡住 = 第 3 步从未发生。
Future 与协议:StreamReader 直觉
asyncio StreamReader.read(n) await 时,若缓冲区空,内部 Future pending 直到 protocol feed_data set_result。你 await reader.read() 就是 await 那个 Future——语法是 协程,机制是 Future pending → done。
小结补充:与下一章 asyncio 模式
Future done 后 Task 才可 await 取值;gather 等多 Task done。模式章的 create_task 启动、cancel 打断,都假设你认 Future 状态机——pending 时 cancel 有效,done 后只能读结果。
附录式走读:从零实现「延迟返回值」
假设没有 asyncio.sleep,只有 loop.call_later 与 Future,你可以手写最小 sleep:
import asyncio
async def my_sleep(delay: float) -> None:
loop = asyncio.get_running_loop()
fut = loop.create_future()
loop.call_later(delay, fut.set_result, None)
await fut
async def main() -> None:
print("t0")
await my_sleep(0.05)
print("t1")
asyncio.run(main())my_sleep 的 Future 在 pending 等 call_later set_result(None);await fut 的 协程 暂停。这就是 Future 作为 时间 与 结果 桥梁的原型——标准库 sleep 只是更完整版本(含 cancel、精度处理)。
为何 Task 必须是 Future 子类
调度器 需要统一接口:await、done_callback、cancel、exception()。若 Task 与 Future 分裂,gather 要写两套逻辑。继承 让 Task 可直接 await,且 isinstance(t, asyncio.Future) 为真——统一 pending 语义。
读取 pending 状态的工具
fut.done()、fut.cancelled()、fut.exception()(done 且失败时)可在 add_done_callback 或断言里用。pending 时 fut.done() 为 False——简单但有效。调试 hung:all_tasks 里找 Task,对每个 await 链 查 Future 是否 set_result。
Future 与 CancelledError 的特殊性
cancelled Future await 抛 CancelledError(BaseException 子类,许多 except Exception 捕不到)。业务 cleanup 用 finally;框架 捕 CancelledError 后 often re-raise。Task.cancel 与 Future.cancel 都走这条语义链。
与 gather:多个 Future 同时 pending
gather(t1, t2) 内部 ensure_future,单 协程 await 等 全部 done。每个 Task 各自 Future 状态机;gather 只是 汇合层。任一 Task exception 默认 gather 失败——理解 Future done with exception 如何冒泡。
再谈 set_result 的责任边界
库 在 I/O 回调 set_result;应用 少手写 set_result,多 await 库返回的 Future/协程。应用 手写 create_future 常见于:适配 callback API、线程 call_soon_threadsafe 回 loop、单元测试 mock I/O。责任边界 清晰可减少 pending 永不 done 的 hung。
状态机记忆卡片
- pending + await → 协程 挂起
- pending + cancel → cancelled
- done + result → await 得值
- done + exception → await 抛
- Task = Future + 协程 驱动
卡片贴显示器旁,读 traceback 时对照 Future 状态,比翻文档快。
读者自查:Future 概念是否真懂
合上文章,口头回答:pending 时 await 的 协程 去哪了?set_result 后谁先进 ready?add_done_callback 与 await 谁先谁后?Task 与裸 Future cancel 差在哪?concurrent.futures.Future 为何不能直接 await?答不出就回看对应小节。
与事件循环章的交叉索引
事件循环 维护 ready;Future set_result 把等待 Task 推进 ready;add_done_callback 的 fn 也进 ready。Future 是 loop 与 协程 之间的 状态枢纽——两章应交叉阅读。
生产故障模式:Future 永不 done
hung 请求多数是:Future 永远 pending(对端未回包、忘记 set_result、callback 未 call_soon_threadsafe、死锁 在 sync Lock)。排查:Task 栈 + Future done 位 + 是否 loop 阻塞。不是 async 魔法失灵,是 状态机 未闭合。
扩展阅读方向(概念,非书单)
读 PEP 492 理解 async/await 语法;读 asyncio 文档 Future、Task 节对照源码 Tasks/futures.py;读 Tr asyncio 源码 Future.await。方向对了,API 增删不必从头学。
最后一轮机制巩固:Future 与 I/O 回调伪代码
伪代码表达 socket 可读时 Future 如何 done(非真实源码,助理解):
on_socket_readable(fd):
data = os.read(fd, bufsize)
fut = fd_to_future[fd]
if not fut.done():
fut.set_result(data)协程 await read_future 在 pending 挂起;on_socket_readable 在 loop 线程(或 threadsafe 投递后)set_result;协程 下一 ready 轮 恢复。Task 路径则是 loop 直接 驱动 协程 到 await read_future,之后同上。
为何 asyncio 坚持 Future 单次结果
多次 set_result 会破坏 await 语义:await 期望 一次 结果。流式数据用 async generator 或 Queue.put 多次推送——不同 协议。混淆「单次 Future」与「流」会导致 API 设计灾难。
Task 与 Future 在 typing 中的写法
asyncio.Task[int]、asyncio.Future[bytes] 3.9+ 泛型标注帮助静态检查;运行时仍同一状态机。pending 不是类型参数——是 运行时 位。
合并阅读建议
先读 事件循环(调度 从哪来),再读 Future(pending 如何 done),再读 协程(await 如何 挂起),最后读 模式(create_task/gather)。Future 章是中间枢纽:向上接 loop 的 ready,向下接 协程 的 await。set_result 一词贯穿 I/O 库与 Task 完成——抓住 状态机 就不怕 API 表面杂。
手写测试:Future 是否 done
import asyncio
async def test_future_done() -> None:
f = asyncio.get_running_loop().create_future()
assert not f.done()
f.set_result(0)
assert f.done()
assert f.result() == 0
asyncio.run(test_future_done())三行断言固化 pending → done——比背定义更接近 运行时 真相。
Future 与 async/await 的历史位置
async/await 语法(PEP 492)让 Future pending 与 协程 挂起 在源码里可读;底层仍是一次次 set_result 与 loop call_soon。Future 概念在 Twisted、Tornado 时代已有;asyncio 把它与 Task、loop 收成标准库一体。学 Future 状态机等于学 Python 3 官方 异步 模型的 ** lingua franca**。
结束语前的检查表
- 能解释 pending / done / cancelled
- 能说出
set_result典型调用者 - 能区分 Task 与裸 Future
- 能说明 add_done_callback 何时跑
- 能
awaitrun_in_executor 返回的 asyncio.Future
五项打勾,Future 章可以收工。
深度:await Future 时 CPython 大致做什么
await fut 触发 fut.__await__(),返回 iterator;Task 驱动该 iterator 直到 StopIteration 携带结果。pending 时 iterator yield 控制权,Task 登记 Future;set_result 后 Task send 恢复 iterator,await 表达式完成。不必读 C 源码,但应知 await 不是 syscall,是 协程 协议 + Future 状态联动。
与第三方 Event Loop
asyncio 也可 plug uvloop 等(第三方 loop 实现),Future/Task 语义不变——仍是 pending/done、set_result、await。事件循环 实现可换,Future 状态机是 asyncio 抽象核心。
Future 章节完整复述(一页纸)
Future 表示单次异步结果;生命周期 pending → done/cancelled;set_result/set_exception 由 I/O、Task 或 threadsafe 回调调用;await 在 pending 挂起 协程;add_done_callback 在 done 后 soon 于 loop 执行;Task 继承 Future 并 驱动 协程 填结果。背下这段,读 httpx/aiohttp 源码时 Future 满屏不会怵。
与 JavaScript Promise 的单向对比(一句)
Future 仅 set 一次,await 在 协程 里;JS Promise then 链 概念 相近,但 Python Task/loop 绑定更紧——不要 把 Node 事件循环经验 原样 套 GIL + asyncio,分工边界不同(见 事件循环 章 多线程 表)。
温故:Future 相关的四个「谁」
谁创建 Future? 常是 loop.create_future 或 Task 内部。谁 set_result? I/O 回调、Task 完成、call_soon_threadsafe。谁 await? 业务 协程。谁 add_done_callback? metrics、logging、链式 I/O。四个 谁 答不清时,pending hung 很难查——建议每遇 hung 在日志里打这四类角色的 id/name。
温故:pending 与 done 的 await 行为对照
| 状态 | await 行为 |
|---|---|
| pending | 挂起 协程,登记等待 |
| done 成功 | 立即得 result,不 挂起 |
| done 异常 | 立即 抛 exception |
| cancelled | 抛 CancelledError |
表适合打印贴在 async 专题笔记首页;与 Task、gather 联用时 对照 trace。
收束
Future 是 asyncio 的「结果插座」:pending 等插,set_result 通电,await 的 协程 灯亮。Task 是带自动插线的插座。整章若只记一句,记:await 的是 Future 状态,不是魔法。
Future 章节建议配合调试器在 set_result 行与 await fut 行各断点一次,亲眼看到 pending 与 done 两侧 协程 栈变化——比再读十页文档更牢固。若只能记一个 API,记 create_future 与 set_result 这对「空信箱与填信箱」组合即可串起全章。下一章 协程 会说明 await 如何挂在这类信箱上。通读后应能用一句话向同事解释:Future 就是 loop 里的一次性结果位。
小结
Future 承载 pending → done 的一次结果或异常;set_result / set_exception 由 I/O 层、Task 或 glue 代码调用。add_done_callback 在 loop 里 稍后 触发。Task 是 Future 子类,把 协程 执行与结果落盘绑在一起。下一篇进入协程本体:async def、await 如何在帧里 暂停与恢复。