AI项目博客
记录 AI 工程实践与大模型应用

FastAPI & SQLAlchemy 实战(二):async/await 与事件循环,协程怎么被调度

「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 做了什么

  1. 当前协程暂停,把控制权交还给事件循环;
  2. 事件循环去执行其他就绪的协程;
  3. 等待的 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 等待,下一篇详细讲。

四、事件循环的六步

  1. 注册:asyncio.run() 把顶级协程包装成 Task;
  2. 启动:事件循环选择一个就绪 Task 执行;
  3. 遇到 await:协程挂起,I/O 注册到事件循环;
  4. 切换:事件循环执行下一个就绪协程;
  5. I/O 完成:操作系统通知事件循环;
  6. 恢复:挂起的协程重新变为就绪,继续执行。

五、顶级 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、超时与取消

赞(0)
未经允许不得转载:AI项目博客 » FastAPI & SQLAlchemy 实战(二):async/await 与事件循环,协程怎么被调度

评论 抢沙发

AI项目博客

专注 AI 工程化落地 —— 覆盖智能体、大模型、Java AI 与 Python 的学习笔记,以及 AI 平台的项目实战记录。

关于本站文章归档

登录

找回密码

注册