Skip to content

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 往前跑到下一个 awaitreturn 或异常。用代码简化一下是这样:

python
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 的实现直觉

python
await asyncio.sleep(1)

线程没有真的睡在那里。发生的是:

  1. 创建一个 Future。
  2. loop.call_later(1, future.set_result, None) 安排定时器。
  3. 当前 Task await 这个 Future,暂停。
  4. 1 秒后 timer callback 运行,Future 完成。
  5. Task 的 wakeup callback 进入 ready 队列。

mini_asyncio 里可以看到这个教学版实现:

python
def sleep(delay, result=None):
    loop = get_running_loop()
    future = loop.create_future()
    loop.call_later(delay, future.set_result, result)
    return future

异常如何传播

如果协程里抛异常:

python
async def fail():
    raise ValueError("boom")

Task 会把异常记在自己身上。之后:

python
task = asyncio.create_task(fail())
await task

await task 会重新抛出 ValueError。如果你从不等待这个 Task,也不调用 task.result(),事件循环之后会提示“Task exception was never retrieved”。

Task 从创建到结束

阶段触发点内部动作
创建create_task(coro)绑定 loop,把 _step 放入 ready
第一次运行事件循环运行 _stepcoro.send(None)
等待协程 await futureTask 暂停,给 future 注册回调
恢复future 完成_wakeup 把结果送回协程
成功结束协程 returnTask 进入 done(result)
失败结束协程 raiseTask 进入 done(exception)
取消task.cancel()下一次恢复时向协程注入 CancelledError

应用层少直接造 Future

写应用时通常不需要手动创建 Future。你更常写:

python
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._step
  • Task._wakeup
  • sleep

给每个函数写一句自己的注释:它什么时候被调用,调用后会让哪个 Future 或 Task 变成 ready。

面向学习目的的 Python asyncio 中文教程与 mini_asyncio 教学运行时。