
在上一篇文章中,我们学习了 Python 异步编程的基础 —— async、await 和 asyncio。
但如果你想真正理解异步代码为什么能“同时”执行多个任务,就必须深入理解 Python 异步的核心:
事件循环(Event Loop)与任务调度(Task Scheduling)机制。
这篇文章将带你从底层视角剖析 asyncio 的工作原理,帮助你彻底理解异步的运行方式。🚀
文章目录
一、异步的核心理念:事件循环(Event Loop)
在异步编程中,我们并没有真正地“并行执行”多个任务。
实际上,是 事件循环(Event Loop) 不断地在多个任务之间切换执行。
🌀 事件循环的工作机制:
- 事件循环维护一个 任务队列(Tasks Queue)
- 每个任务代表一个协程(Coroutine)
- 当协程遇到
await时,会挂起当前任务 - 事件循环将执行权切换到下一个“准备就绪”的任务
- 当 I/O 操作完成后,任务会再次被唤醒继续执行
简单来说:
事件循环 = 协程的调度器
如果想进一步了解异步编程,请参考Python 异步编程详解-CSDN博客
二、Python 中的事件循环结构
在 asyncio 中,一个典型的事件循环运行流程如下:
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
async def main():
await say_hello()
asyncio.run(main())
背后的流程:
asyncio.run()启动事件循环- 将
main()协程注册为一个任务(Task) - 事件循环开始调度任务执行
- 当遇到
await asyncio.sleep(1),任务被挂起 - 事件循环去执行其他任务(如果有)
- 1 秒后事件恢复,继续执行剩余语句
三、协程(Coroutine) vs 任务(Task)
在 asyncio 中有两个非常重要的概念:
| 对象 | 说明 |
|---|---|
| 协程(Coroutine) | 使用 async def 定义的函数,执行时返回一个协程对象 |
| 任务(Task) | 由事件循环管理的协程封装体,可被调度和执行 |
简单理解:
协程是“定义”,任务是“执行”。
示例:协程与任务的区别
import asyncio
async def foo():
print("Running foo...")
await asyncio.sleep(1)
print("Done!")
# 创建协程对象
coro = foo()
# 创建任务
task = asyncio.create_task(foo())
print(type(coro)) # <class 'coroutine'>
print(type(task)) # <class '_asyncio.Task'>
coro是协程对象,它还没被调度执行task是任务对象,事件循环会立即调度它运行
四、任务调度(Task Scheduling)
当我们执行:
asyncio.run(main())
内部实际发生了以下过程:
- 创建事件循环
- 封装协程为 Task
task = asyncio.ensure_future(main())
- 运行事件循环
loop.run_until_complete(task)
- 事件循环持续执行任务队列
- 如果任务遇到
await,则将其挂起 - 当异步操作完成,任务被唤醒重新加入队列
- 如果任务遇到
- 所有任务执行完毕后,事件循环关闭
五、示例:任务并发执行过程
import asyncio
async def task(name, delay):
print(f"{name} 开始")
await asyncio.sleep(delay)
print(f"{name} 结束")
async def main():
tasks = [
asyncio.create_task(task("任务1", 2)),
asyncio.create_task(task("任务2", 1)),
]
await asyncio.gather(*tasks)
asyncio.run(main())
执行顺序说明:
任务1 开始
任务2 开始
任务2 结束 (1秒后)
任务1 结束 (2秒后)
事件循环的调度逻辑:
- 同时启动两个任务
- 当任务1等待 2 秒时,事件循环转去执行任务2
- 任务2完成后,等待任务1结束
- 最终所有任务完成
🧩 事件循环并不是“多线程”,而是高效地在单线程中切换协程执行权。
六、await 的底层机制
当协程遇到 await 时,执行流程如下:
- 当前协程将控制权交还给事件循环
- 事件循环可以切换执行其他协程
- 当被等待的协程执行完成后,当前协程恢复执行
换句话说:
await asyncio.sleep(1)
相当于告诉事件循环:
“我现在要等 1 秒,请先去干别的活,等我好了再回来。”
七、任务状态与生命周期
每个 Task 都有自己的状态:
| 状态 | 说明 |
|---|---|
PENDING | 任务已创建但未运行 |
RUNNING | 任务正在执行 |
DONE | 任务执行完毕 |
CANCELLED | 任务被取消 |
可以通过以下方式查看状态:
import asyncio
async def demo():
await asyncio.sleep(1)
async def main():
task = asyncio.create_task(demo())
print(task.done()) # False
await asyncio.sleep(1.5)
print(task.done()) # True
asyncio.run(main())
八、自定义简单事件循环(理解本质)
为了更好理解事件循环的原理,我们可以模拟一个简化版本:
import time
tasks = []
def add_task(task):
tasks.append(task)
def run():
while tasks:
task = tasks.pop(0)
try:
next(task)
tasks.append(task)
except StopIteration:
pass
def coroutine():
for i in range(3):
print(f"任务执行中 {i}")
yield # 暂停(模拟 await)
# 添加任务
add_task(coroutine())
run()
输出:
任务执行中 0
任务执行中 1
任务执行中 2
这段代码展示了事件循环的核心思想:
“一个调度器,不断轮询任务,遇到挂起就切换执行其他任务。”
⚡ 九、asyncio 的执行模型总结
| 概念 | 作用 |
|---|---|
| 协程(coroutine) | 使用 async def 定义的异步函数 |
| 任务(task) | 协程的执行单元,由事件循环调度 |
| 事件循环(event loop) | 控制协程的执行顺序与切换 |
| await | 暂停协程执行,等待异步结果 |
| asyncio.run() | 启动事件循环,执行异步程序入口 |
整体流程如下:
asyncio.run(main())
↓
创建事件循环
↓
封装协程为任务(Task)
↓
任务进入调度队列
↓
事件循环按顺序调度执行
↓
遇到 await 时挂起并切换
↓
I/O 完成后恢复执行
↓
所有任务结束,关闭事件循环
十、总结
| 关键点 | 说明 |
|---|---|
| 事件循环 | 异步程序的心脏,调度所有协程任务 |
| 协程 | 一种可暂停的函数(async def) |
| 任务 | 被事件循环管理的协程执行体 |
| await | 用于挂起当前协程,等待另一个协程完成 |
| asyncio.run() | 启动事件循环的入口 |
107

被折叠的 条评论
为什么被折叠?



