17370845950

Python async/await 执行流程详解
async函数调用后立刻返回一个coroutine对象,该对象未执行,仅表示待调度的异步任务;必须通过await或asyncio.run()驱动运行。

async 函数调用后立刻返回什么

调用 async def 定义的函数,不会真正执行函数体,而是立即返回一个 coroutine 对象。这个对象本身不运行,只是个待调度的

“任务描述”。

常见错误是直接打印或忽略返回值,误以为函数已执行:

async def fetch_data():
    return "done"

result = fetch_data() # ← 这里 result 是 print(result) # ,不是 "done"

  • 必须用 await(在另一个 async 函数内)或 asyncio.run() 驱动它运行
  • await 只能在 async def 函数内部使用,不能在普通函数或交互式顶层直接写
  • 试图对 coroutine 对象做 result + "!" 会报 TypeError: unsupported operand type(s)

await 表达式到底做了什么

await 不是“等待完成”,而是“让出控制权,挂起当前协程,把 CPU 让给事件循环去跑别的协程”。它只在遇到真正的异步挂起点(如 asyncio.sleep()aiohttp.get()await asyncio.Lock().acquire())时才暂停;纯计算不会挂起。

示例中看似“同步”的代码,实际可能不阻塞:

async def work():
    print("start")
    await asyncio.sleep(0)  # ← 真正的挂起点
    print("after sleep")
    x = sum(i for i in range(10**6))  # ← 纯计算,不挂起,会阻塞整个事件循环
  • await 后面必须是可等待对象(Awaitable),比如 coroutineTaskFuture,不能是普通函数返回值或数字
  • await 42 会报 TypeError: object int can't be used in 'await' expression
  • 多次 await 同一个协程对象会报 RuntimeError: cannot reuse already awaited coroutine

asyncio.run() 的隐含行为

asyncio.run(main()) 不只是启动协程,它会:新建一个事件循环、把 main() 包装成 Task、运行直到完成、然后关闭循环并清理所有未完成的 Task(触发 CancelledError)。

这意味着:

  • 不能在已有事件循环的环境中(如 Jupyter、Django/Flask 异步视图、或已调用 asyncio.get_event_loop() 的脚本)重复调用 asyncio.run(),会报 RuntimeError: asyncio.run() cannot be called from a running event loop
  • main() 内部启用了后台 Task(如 asyncio.create_task(...)),且没显式 awaitasyncio.wait_for(),它们可能被强制取消
  • asyncio.run() 总是创建新循环,所以无法跨调用共享 asyncio.Queueasyncio.Event 等对象

Task 和 create_task() 的生命周期管理

asyncio.create_task() 提交的协程会立即被调度,但它的完成时间不可控——你得自己决定是否等待它、如何捕获异常、是否允许它在主协程退出后继续跑。

典型陷阱:

async def background_job():
    await asyncio.sleep(2)
    print("done")

async def main(): asyncio.create_task(background_job()) # ← 没有变量接收,也没 await await asyncio.sleep(0.1) # 主协程很快结束

这段代码大概率看不到 "done" 输出,因为 background_job() 被创建后还没来得及执行完,事件循环就退出了。

  • 推荐保存 task 到变量:task = asyncio.create_task(...),再用 await taskasyncio.gather(task, ...)
  • 若需“发射即忘”,至少加 asyncio.shield(task) 防止被取消,或用 asyncio.create_task(..., name="xxx") 方便调试
  • 未被 await 的 task 报错时(如网络异常),错误会被静默吞掉,除非你调用 task.exception() 主动检查

真正难的不是语法,是判断某段逻辑该不该拆成 async、哪个 await 实际构成瓶颈、以及什么时候该用 Task 而不是链式 await。这些没法靠记住规则解决,得看 asyncio.Task.all_tasks()asyncio.current_task() 在运行时吐出什么。