看源码从哪里开始
CPython 的 asyncio 源码集中在 Lib/asyncio/。别一上来从所有文件平铺阅读。先盯住一个问题:asyncio.run(main()) 到底怎么把 main() 跑完?
先看哪些文件
| 文件 | 先看什么 |
|---|---|
runners.py | asyncio.run()、Runner 如何创建、运行、关闭事件循环 |
base_events.py | BaseEventLoop、run_until_complete()、_run_once() 主循环 |
events.py | 事件循环接口、Handle、TimerHandle |
tasks.py | Task、create_task()、gather()、取消处理 |
futures.py | Future 状态、回调、结果和异常 |
queues.py | Queue、PriorityQueue、QueueShutDown |
streams.py | StreamReader、StreamWriter 和高层连接 API |
locks.py | Lock、Event、Condition、Semaphore、Barrier |
selector_events.py | Unix 常见 selector 事件循环的 socket I/O 细节 |
proactor_events.py | Windows proactor 事件循环相关实现 |
asyncio.run 会走到哪些函数
从 asyncio.sleep 开始怎么读源码
可以按这个顺序看:
tasks.py里的sleep()。- 它如何创建 Future。
- 它如何调用
loop.call_later()安排完成 Future。 events.py里的TimerHandle。base_events.py里的_run_once()如何把到期 timer 移到 ready。- Future 完成后如何调用 Task 的 wakeup。
这一段比较短,但会经过定时器、Future、Task 和事件循环。
从 Stream read 开始怎么读源码
读 Stream 的过程比 sleep() 复杂,因为 Streams 下面还有 Transport/Protocol。应用层通常不用自己写 Protocol,但读源码时要知道它在中间接了一层。
对照 mini_asyncio
| CPython asyncio | mini_asyncio | 学习重点 |
|---|---|---|
BaseEventLoop._run_once | EventLoop._run_once | timer 到点、socket 可读以后怎么进入 ready 队列 |
Future | Future | 结果状态和 done callback |
Task | Task | coroutine.send、等待 Future、wakeup |
asyncio.sleep | sleep | timer 如何完成 Future |
gather | gather | 多个 Future 如何聚合 |
Queue | AsyncQueue | getter Future 如何被 put 唤醒 |
loop.sock_recv | sock_recv | selector readable 如何完成 Future |
阅读时问四个问题
每看到一个函数,先问:
- 它是应用代码会直接调用的 API,还是 loop 内部会调用的函数?
- 它同步返回结果,还是返回一个 Future/Task 等以后完成?
- 它把 callback 放进了哪个队列?
- 异常和取消先记录在哪里,最后由谁重新抛出来?
这四个问题比死记文件名更有用。
一开始先别读什么
先别急着钻这些地方:
- 所有平台分支细节。
- SSL 协议状态机。
- 子进程和信号处理的所有细节。
- 每个版本的兼容处理。
先顺着 run()、_run_once()、Future、Task、sleep()、Queue、Streams 这些点读一遍。平台差异和特殊分支可以先放一边。
小练习
打开 CPython 仓库的 Lib/asyncio/tasks.py,找出 sleep()、gather()、TaskGroup 分别在哪些文件或类中实现。然后对照本仓库的 mini_asyncio,写出哪些功能被保留、哪些被故意省略。