「FastAPI & SQLAlchemy 实战」系列共 10 篇,从协程基础讲到数据库接口。这是第 2 篇。
结论:async def 定义协程函数,调用它只返回协程对象;await 让出控制权,事件循环负责在 I/O 完成后恢复执行。
一、async def 不是“立即执行”
import asyncio
async def hello():
print("Hello")
await asyncio.sleep(1)
print("World")
coro = hello() # 不会执行,只返回 coroutine 对象
print(type(coro)) # <class 'coroutine'>
asyncio.run(coro) # 启动事件循环,协程才真正运行
直接调用 hello() 不会打印任何东西。协程必须交给事件循环,才会被调度。
二、await 做了什么
- 当前协程暂停,把控制权交还给事件循环;
- 事件循环去执行其他就绪的协程;
- 等待的 I/O 完成后,事件循环恢复当前协程,从
await下一行继续。
await 只能写在 async def 函数内部。
三、串行 vs 并发
async def my_func(name, delay):
print(f"{name} 开始")
await asyncio.sleep(delay)
print(f"{name} 完成")
return name
async def serial():
await my_func("A", 1)
await my_func("B", 2) # 总耗时约 3 秒
async def concurrent():
results = await asyncio.gather(
my_func("A", 1),
my_func("B", 2),
) # 总耗时约 2 秒
print(results)
await 是串行等待:A 完成后才执行 B。要让多个任务同时跑,需要把它们包装成 Task,再用 gather 等待,下一篇详细讲。
四、事件循环的六步
- 注册:
asyncio.run()把顶级协程包装成 Task; - 启动:事件循环选择一个就绪 Task 执行;
- 遇到 await:协程挂起,I/O 注册到事件循环;
- 切换:事件循环执行下一个就绪协程;
- I/O 完成:操作系统通知事件循环;
- 恢复:挂起的协程重新变为就绪,继续执行。
五、顶级 Task 退出会取消子 Task
async def main():
asyncio.create_task(my_func("A", 1))
asyncio.create_task(my_func("B", 2))
# main 立即结束,事件循环退出,两个子 Task 被取消
只 create_task 不等待,顶级协程结束后子任务会被取消。必须用 gather 或 await 等它们完成。
六、常见错误
- 在
async def里调用time.sleep():会阻塞整个事件循环,用await asyncio.sleep(); - 只创建协程对象不
await:代码不会执行; - 在同步函数里写
await:语法错误; - 以为协程能利用多核:它只在单线程内并发。
下一篇
下一篇讲并发控制的四个工具:create_task、gather、wait_for、cancel。
上一篇:线程、进程、协程,到底差在哪 | 下一篇:Task、gather、超时与取消

AI项目博客






