Future 和 Task 从哪来、到哪去
前面的练习里你很少直接创建 Future,但一直在间接使用它。asyncio.sleep()、reader.readline()、queue.get() 都需要一个地方保存“以后才会有的结果”。这个地方就是 Future。Task 则负责推动 coroutine 往下跑。
Future 状态机
Future 要做的事不多:
done():结果是否已经可用。result():取结果,失败时重新抛出异常。set_result():生产者写入成功结果。set_exception():生产者写入失败。add_done_callback():完成后通知等待者。__await__():让 Future 可以被await。
比如 socket 现在还没数据,内部代码可以先给你一个 Future。等 socket 可读了,再把读到的数据写进这个 Future。你的业务代码只需要 await 它,不用自己写轮询。
Task 如何让 coroutine 继续执行
Task 包着一个 coroutine。事件循环叫醒 Task 时,Task 会让 coroutine 往前跑到下一个 await、return 或异常。用代码简化一下是这样:
try:
yielded = coro.send(value)
except StopIteration as exc:
task.set_result(exc.value)
except BaseException as exc:
task.set_exception(exc)
else:
yielded.add_done_callback(task._wakeup)第一遍看源码,先抓住这几种情况:
sleep 的实现直觉
await asyncio.sleep(1)线程没有真的睡在那里。发生的是:
- 创建一个 Future。
- 用
loop.call_later(1, future.set_result, None)安排定时器。 - 当前 Task await 这个 Future,暂停。
- 1 秒后 timer callback 运行,Future 完成。
- Task 的 wakeup callback 进入 ready 队列。
在 mini_asyncio 里可以看到这个教学版实现:
def sleep(delay, result=None):
loop = get_running_loop()
future = loop.create_future()
loop.call_later(delay, future.set_result, result)
return future异常如何传播
如果协程里抛异常:
async def fail():
raise ValueError("boom")Task 会把异常记在自己身上。之后:
task = asyncio.create_task(fail())
await taskawait task 会重新抛出 ValueError。如果你从不等待这个 Task,也不调用 task.result(),事件循环之后会提示“Task exception was never retrieved”。
Task 从创建到结束
| 阶段 | 触发点 | 内部动作 |
|---|---|---|
| 创建 | create_task(coro) | 绑定 loop,把 _step 放入 ready |
| 第一次运行 | 事件循环运行 _step | coro.send(None) |
| 等待 | 协程 await future | Task 暂停,给 future 注册回调 |
| 恢复 | future 完成 | _wakeup 把结果送回协程 |
| 成功结束 | 协程 return | Task 进入 done(result) |
| 失败结束 | 协程 raise | Task 进入 done(exception) |
| 取消 | task.cancel() | 下一次恢复时向协程注入 CancelledError |
应用层少直接造 Future
写应用时通常不需要手动创建 Future。你更常写:
await asyncio.sleep(1)
await reader.readline()
await queue.get()这些 API 内部已经帮你创建 Future、注册 I/O callback、设置结果。只有当你在写框架、封装 callback API,或自己实现 Queue、Lock 这类工具时,才更常直接使用 loop.create_future()。
小练习
打开 examples/mini_asyncio_runtime/mini_asyncio/core.py,找到:
Future.__await__Task._stepTask._wakeupsleep
给每个函数写一句自己的注释:它什么时候被调用,调用后会让哪个 Future 或 Task 变成 ready。