Skip to content

看源码从哪里开始

CPython 的 asyncio 源码集中在 Lib/asyncio/。别一上来从所有文件平铺阅读。先盯住一个问题:asyncio.run(main()) 到底怎么把 main() 跑完?

先看哪些文件

文件先看什么
runners.pyasyncio.run()Runner 如何创建、运行、关闭事件循环
base_events.pyBaseEventLooprun_until_complete()_run_once() 主循环
events.py事件循环接口、Handle、TimerHandle
tasks.pyTaskcreate_task()gather()、取消处理
futures.pyFuture 状态、回调、结果和异常
queues.pyQueuePriorityQueueQueueShutDown
streams.pyStreamReaderStreamWriter 和高层连接 API
locks.pyLock、Event、Condition、Semaphore、Barrier
selector_events.pyUnix 常见 selector 事件循环的 socket I/O 细节
proactor_events.pyWindows proactor 事件循环相关实现

asyncio.run 会走到哪些函数

从 asyncio.sleep 开始怎么读源码

可以按这个顺序看:

  1. tasks.py 里的 sleep()
  2. 它如何创建 Future。
  3. 它如何调用 loop.call_later() 安排完成 Future。
  4. events.py 里的 TimerHandle
  5. base_events.py 里的 _run_once() 如何把到期 timer 移到 ready。
  6. Future 完成后如何调用 Task 的 wakeup。

这一段比较短,但会经过定时器、Future、Task 和事件循环。

从 Stream read 开始怎么读源码

读 Stream 的过程比 sleep() 复杂,因为 Streams 下面还有 Transport/Protocol。应用层通常不用自己写 Protocol,但读源码时要知道它在中间接了一层。

对照 mini_asyncio

CPython asynciomini_asyncio学习重点
BaseEventLoop._run_onceEventLoop._run_oncetimer 到点、socket 可读以后怎么进入 ready 队列
FutureFuture结果状态和 done callback
TaskTaskcoroutine.send、等待 Future、wakeup
asyncio.sleepsleeptimer 如何完成 Future
gathergather多个 Future 如何聚合
QueueAsyncQueuegetter Future 如何被 put 唤醒
loop.sock_recvsock_recvselector readable 如何完成 Future

阅读时问四个问题

每看到一个函数,先问:

  1. 它是应用代码会直接调用的 API,还是 loop 内部会调用的函数?
  2. 它同步返回结果,还是返回一个 Future/Task 等以后完成?
  3. 它把 callback 放进了哪个队列?
  4. 异常和取消先记录在哪里,最后由谁重新抛出来?

这四个问题比死记文件名更有用。

一开始先别读什么

先别急着钻这些地方:

  • 所有平台分支细节。
  • SSL 协议状态机。
  • 子进程和信号处理的所有细节。
  • 每个版本的兼容处理。

先顺着 run()_run_once()FutureTasksleep()QueueStreams 这些点读一遍。平台差异和特殊分支可以先放一边。

小练习

打开 CPython 仓库的 Lib/asyncio/tasks.py,找出 sleep()gather()TaskGroup 分别在哪些文件或类中实现。然后对照本仓库的 mini_asyncio,写出哪些功能被保留、哪些被故意省略。

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