1. Future
在异步程序中,一个操作开始时,结果往往还没有准备好。例如,网络库已经发出了请求,但响应要稍后才能到达;某个底层回调已经注册,但回调什么时候触发还不确定。
等待方需要一个可以表达「结果将来会出现」的对象,这就是 asyncio.Future。
Future 本身不会执行网络请求,也不会启动一段协程。它只是保存一个尚未产生的结果,并在结果准备好时通知等待者。可以把参与者分成两端:
| 角色 | 职责 |
|---|---|
| 生产者 | 在未来设置结果、异常,或者取消 Future |
| 等待者 | 使用 await 等待 Future 完成 |
| Future | 在两者之间保存状态和最终结果 |
01import asyncio0203async def produce(future):04await asyncio.sleep(1)0506if not future.cancelled():07future.set_result("数据已经准备好")0809async def consume(future):10print("等待结果")11result = await future12print(result)1314async def main():15loop = asyncio.get_running_loop()16future = loop.create_future()1718producer = asyncio.create_task(produce(future))19await consume(future)20await producer2122asyncio.run(main())
代码运行到 await future 时,如果 Future 尚未完成,当前的 consume Task 会暂停,并把执行机会交还给事件循环。事件循环继续推进 produce Task。生产者调用 set_result() 后,Future 进入完成状态,事件循环随后恢复等待它的 Task。
注意,set_result() 的职责是完成 Future,而不是在这一行直接跳进 consume() 继续执行。等待者由事件循环安排在后续调度机会中恢复。
Future 与前面学过的两个对象各自负责不同的事情:
| 对象 | 表达的内容 | 是否负责执行 |
|---|---|---|
| 协程对象 | 一次异步调用 | 否,需要被等待或调度 |
| Task | 正在由事件循环推进的协程 | 是 |
| Future | 一个稍后才能确定的结果 | 否 |
因此,不能把 Future 理解成一个「还没开始的任务」。它更像 Task 或底层异步操作正在等待的结果凭证。
2. 创建
在协程中创建 Future 时,推荐先取得当前事件循环,再调用 loop.create_future():
1import asyncio23async def main():4loop = asyncio.get_running_loop()5future = loop.create_future()67print(future.done()) # False89asyncio.run(main())
虽然也能直接调用 asyncio.Future(),但 loop.create_future() 会明确地把 Future 绑定到当前事件循环,也允许第三方事件循环提供与自身适配的实现。因此,编写异步库和底层适配代码时应优先使用这种方式。
刚创建的 Future 处于未完成状态。创建它不会自动安排任何工作,如果没有其他代码负责完成它,下面的程序会一直等待:
01import asyncio0203async def main():04loop = asyncio.get_running_loop()05future = loop.create_future()0607# 没有任何生产者调用 set_result()、set_exception() 或 cancel()08# await future # 将一直等待0910asyncio.run(main())
这也解释了为什么普通业务代码很少需要手动创建 Future:Future 通常由网络库、驱动程序、事件循环适配层创建,业务代码只负责等待库返回的协程或 Task。
同一个 Future 可以被多个 Task 等待。Future 完成后,所有等待者都会取得同一个结果:
01import asyncio0203async def wait_result(name, future):04result = await future05print(f"{name} 收到:{result}")0607async def main():08loop = asyncio.get_running_loop()09future = loop.create_future()1011first = asyncio.create_task(wait_result("A", future))12second = asyncio.create_task(wait_result("B", future))1314await asyncio.sleep(0.1)15future.set_result("完成")1617await asyncio.gather(first, second)1819asyncio.run(main())
Future 完成后会保留结果,所以还可以再次 await 它,新的等待不会让生产者重新执行:
01import asyncio0203async def main():04loop = asyncio.get_running_loop()05future = loop.create_future()06future.set_result("已缓存的结果")0708print(await future)09print(await future)1011asyncio.run(main())
这和直接重复等待同一个协程对象不同。协程对象只能被完整执行一次,而完成后的 Future 可以反复读取同一个最终结果。
3. 状态
Future 创建后处于 pending,也就是未完成状态。之后只能走向三种终态之一:
| 状态 | 如何进入 | 等待时发生什么 |
|---|---|---|
| 正常完成 | set_result(value) | await 返回 value |
| 异常完成 | set_exception(error) | await 抛出该异常 |
| 已取消 | cancel() | await 抛出 CancelledError |
正常、异常和取消都属于「已经完成」,所以这三种情况下 done() 都返回 True。终态不能再切换到其他状态,一个 Future 只能被完成一次。
01import asyncio0203async def main():04loop = asyncio.get_running_loop()05future = loop.create_future()0607print(future.done()) # False08print(future.cancelled()) # False0910future.set_result("成功")1112print(future.done()) # True13print(future.cancelled()) # False14print(future.result()) # 成功1516asyncio.run(main())
常用状态方法如下:
| 方法 | 含义 |
|---|---|
done() | 是否已经正常完成、异常完成或被取消 |
cancelled() | 是否最终进入取消状态 |
result() | 读取结果;失败时重新抛出异常 |
exception() | 读取异常;正常完成时返回 None |
get_loop() | 取得 Future 所属的事件循环 |
如果 Future 还没有完成,直接调用 result() 或 exception() 会抛出 asyncio.InvalidStateError。在业务代码中,应优先使用 await future,因为 await 会在结果未准备好时暂停,而不是提前读取:
01import asyncio0203async def main():04loop = asyncio.get_running_loop()05future = loop.create_future()0607# future.result() # InvalidStateError0809loop.call_later(1, future.set_result, "完成")10result = await future11print(result)1213asyncio.run(main())
set_result() 和 set_exception() 也只能调用一次。如果 Future 已经完成或取消,再设置结果会抛出 InvalidStateError。生产者可能晚于取消请求返回结果时,应该先检查取消状态:
1def complete(future, value):2if not future.cancelled():3future.set_result(value)
这项检查主要用于底层回调适配。普通业务代码不应到处用 done() 和 cancelled() 手写一套并发状态机。
4. 异常
生产者无法得到正常结果时,可以使用 set_exception() 完成 Future。等待者不需要检查某个特殊返回值,直接使用 try...except 处理异常:
01import asyncio0203async def main():04loop = asyncio.get_running_loop()05future = loop.create_future()0607future.set_exception(ValueError("响应格式错误"))0809try:10await future11except ValueError as error:12print(error)1314asyncio.run(main())
如果不等待这个 Future,也没有调用 exception() 取走异常,事件循环可能在 Future 被回收时报告 Future exception was never retrieved。这通常说明程序启动了异步工作,却没有负责处理它的失败。
调用 future.cancel() 会尝试把未完成的 Future 标记为取消。等待它的代码会收到 asyncio.CancelledError:
01import asyncio0203async def main():04loop = asyncio.get_running_loop()05future = loop.create_future()0607future.cancel("用户停止了等待")0809try:10await future11except asyncio.CancelledError as error:12print(error)1314print(future.done()) # True15print(future.cancelled()) # True1617asyncio.run(main())
如果 Future 已经正常或异常完成,cancel() 不会覆盖原来的结果,并返回 False。如果取消成功,则返回 True。
当一个 Task 正在等待 Future,而这个 Task 被取消时,取消通常也会传播到它正在等待的 Future。取消不是普通的失败结果,而是异步调用链停止工作的控制信号;处理 CancelledError 时,完成资源清理后通常应该继续抛出。
5. 回调
Future 除了可以被 await,还可以通过 add_done_callback() 注册完成回调。回调只接收一个参数,也就是已经完成的 Future:
01import asyncio0203def print_result(future):04if future.cancelled():05print("Future 已取消")06return0708error = future.exception()09if error is not None:10print(f"执行失败:{error}")11return1213print(f"收到:{future.result()}")1415async def main():16loop = asyncio.get_running_loop()17future = loop.create_future()18future.add_done_callback(print_result)1920future.set_result("消息")21print("结果已经设置")2223await asyncio.sleep(0)2425asyncio.run(main())
调用 set_result() 后,回调不会嵌套在这一行中立即执行,而是通过事件循环安排执行。即使注册回调时 Future 已经完成,回调也会被安排到后续调度机会。
对于普通业务流程,await 往往更直观。add_done_callback() 常见于底层库、监控逻辑和需要兼容回调接口的代码。
Future 最重要的用途之一,就是把旧式回调 API 转换成可等待的异步 API。假设一个旧函数通过回调返回错误或数据:
01import asyncio0203def legacy_request(loop, callback):04loop.call_later(1, callback, None, {"name": "小微"})0506async def request():07loop = asyncio.get_running_loop()08future = loop.create_future()0910def on_complete(error, data):11if future.cancelled():12return1314if error is not None:15future.set_exception(error)16else:17future.set_result(data)1819legacy_request(loop, on_complete)20return await future2122async def main():23result = await request()24print(result)2526asyncio.run(main())
转换过程可以分成四步:
- 适配函数先创建一个 Future;
- 把回调交给旧 API;
- 回调触发后,用结果或异常完成 Future;
- 对外返回
await future的结果。
调用者看到的是普通的 await request(),不再需要理解底层什么时候调用回调。Future 正是在这里连接了「回调世界」和 async / await 世界。
6. Task
Task 和 Future 都是可等待对象,都能保存最终结果、异常和取消状态,但它们的职责并不相同。
Task 是一种 Future 风格的对象,它在 Future 的状态能力之上增加了「推进协程」的职责。协程 return 的值会成为 Task 的结果,协程抛出的异常会成为 Task 的异常;调用者不能使用 set_result() 或 set_exception() 任意决定 Task 的结果。
01import asyncio0203async def fetch_user():04await asyncio.sleep(0.1)05return {"name": "小微"}0607async def main():08task = asyncio.create_task(fetch_user())0910print(asyncio.isfuture(task)) # True11print(await task) # {'name': '小微'}12print(task.result()) # {'name': '小微'}1314asyncio.run(main())
asyncio.isfuture(task) 返回 True,说明 Task 符合 Future 协议,可以像 Future 一样被等待和检查状态。但 Task 的结果来源于协程执行,普通 Future 的结果则由外部生产者设置。
| 对比 | Future | Task |
|---|---|---|
| 是否保存结果和异常 | 是 | 是 |
是否可以 await | 是 | 是 |
| 是否主动推进协程 | 否 | 是 |
| 结果由谁决定 | 外部生产者 | 协程的 return 或异常 |
| 常见创建方式 | loop.create_future() | asyncio.create_task() |
| 主要使用位置 | 异步库、驱动和回调适配层 | 应用中的并发任务 |
当 Task 执行到 await future 时,可以把过程理解为:
- Task 检查 Future 是否完成;
- 如果尚未完成,Task 暂停;
- Future 记录这个等待关系;
- Future 完成后,事件循环让 Task 恢复;
await返回结果,或者在 Task 中抛出异常。
这条链路回答了一个关键问题:Task 被创建后并不是持续占用 CPU。它遇到尚未完成的 Future 时会暂停,事件循环可以去推进其他就绪 Task。
7. 边界
Future 是较底层的工具。编写普通 FastAPI 或 LangChain 业务时,通常应该返回协程的结果,而不是把裸 Future 暴露给调用者:
01import asyncio0203async def load_user():04await asyncio.sleep(0.1)05return {"name": "小微"}0607async def main():08user = await load_user()09print(user)1011asyncio.run(main())
这样,Future 的创建、完成和取消都留在实现底层。调用者只需要使用稳定的异步函数接口。官方文档也建议,不要在面向业务使用者的 API 中直接暴露 Future。
ensure_future()
asyncio.ensure_future() 能接收 Future、Task、协程或其他可等待对象,并保证返回一个 Future 风格的对象:
01import asyncio0203async def work():04return "完成"0506async def main():07task = asyncio.ensure_future(work())08print(await task)0910asyncio.run(main())
如果参数本来就是 Future 或 Task,它会被直接返回;如果参数是协程,则会被包装成 Task 并安排执行。现代应用代码已经明确拿到协程时,通常优先使用语义更清楚的 asyncio.create_task();管理一组相关任务时,优先考虑 TaskGroup。
线程边界
asyncio.Future 不是线程安全对象。Future 应在它所属的事件循环线程中操作。如果工作在线程池或其他线程中完成,不应直接从那个线程调用 future.set_result(),而要使用线程安全的事件循环入口:
01import asyncio02import threading0304def worker(loop, future):05result = "线程中的计算结果"06loop.call_soon_threadsafe(future.set_result, result)0708async def main():09loop = asyncio.get_running_loop()10future = loop.create_future()1112thread = threading.Thread(target=worker, args=(loop, future))13thread.start()1415print(await future)16thread.join()1718asyncio.run(main())
Python 还有另一种 concurrent.futures.Future,用于线程池和进程池。它与 asyncio.Future 不是同一个类型,不能直接在异步代码中 await。需要桥接已有的 concurrent.futures.Future 时,可以使用 asyncio.wrap_future();调用 asyncio.to_thread() 或事件循环的执行器 API 时,库通常已经替你处理了这层转换。
Promise 对比
Future 经常被类比为 JavaScript Promise,因为二者都表示未来的结果,但不能把它们当成完全相同的对象:
| 对比 | asyncio.Future | JavaScript Promise |
|---|---|---|
| 是否表示未来结果 | 是 | 是 |
| 创建时是否自带执行器 | 否 | new Promise(executor) 会立即调用执行器 |
| 谁完成结果 | 外部生产者调用 Future 方法 | resolve 或 reject |
| 完成后的调度 | 由 asyncio 事件循环调度等待者和回调 | .then() 等进入微任务队列 |
| 是否线程安全 | 否 | JavaScript 通常在自身事件循环模型中使用 |
因此,Future 只负责承载结果,不等于 JavaScript 中会立即运行 executor 的 Promise。Python 也不使用浏览器里的宏任务、微任务规则来解释 Future 的恢复顺序。