在这里插入图片描述
我帮你把这两段代码的区别、为什么要封装在 async def main() 里、await 为什么不能随便用,一次性讲透。


一、核心结论先给你

  1. 为什么要写 async def main()
    因为 await 关键字只能在 async def 定义的协程函数里用,不能直接写在顶层代码。

  2. 两段代码的本质区别:

    • 左边代码:边执行边处理结果(哪个任务先跑完,就先处理哪个)
    • 右边代码:所有任务跑完后,再一次性遍历结果(结果顺序固定,按任务列表顺序打印)

二、先解决你的核心疑问:await 为什么不能写在外面?

1. 什么是 await

await 是 Python 异步编程里的关键字,作用是:

  • 暂停当前协程,等待一个可等待对象(协程、Future、Task)完成
  • 期间事件循环可以去调度其他协程

它有一个硬性语法限制:

await 只能出现在 async def 定义的函数内部,否则会直接报语法错误。

你可以把它理解为:

  • 只有“协程函数”才有资格用 await 去“等”东西
  • 普通函数/顶层代码,没有这个资格

2. 为什么左边要封装在 async def main() 里?

看左边代码:

async def main():
    tasks = []
    for url in range(20):
        url = "http://xxx/{}".format(url)
        tasks.append(asyncio.ensure_future(get_url(url)))
    for task in asyncio.as_completed(tasks):
        result = await task  # 这里用到了 await
        print(result)
  • await task 必须在 async def 里,所以必须把这部分逻辑包进 main() 协程
  • 然后在 if __name__ == "__main__": 里,用 loop.run_until_complete(main()) 启动这个协程

3. 为什么右边不能在 run_until_complete 里直接打印?

右边代码:

tasks = []
for url in range(20):
    url = "http://xxx/{}".format(url)
    tasks.append(asyncio.ensure_future(get_url(url)))

loop.run_until_complete(asyncio.wait(tasks))  # 等所有任务跑完

# 这里是普通顶层代码,不是协程函数
for task in tasks:
    print(task.result())  # 用 task.result() 拿结果,不用 await
  • 这里不能用 await task,因为外面不是协程函数
  • 但任务已经全部跑完了,所以可以直接用 task.result() 拿到结果,不用 await

三、两段代码的执行流程 & 区别对比

左边:asyncio.as_completed 版本(边跑边处理)

async def main():
    tasks = [...]  # 生成20个任务
    for task in asyncio.as_completed(tasks):
        result = await task
        print(result)

执行流程:

  1. 事件循环启动 main() 协程
  2. 循环遍历 as_completed(tasks),这个迭代器会按任务完成的先后顺序返回已完成的任务
  3. 对每个已完成的任务,用 await task 拿到结果,然后立刻 print
  4. 哪个请求先返回,哪个就先打印,顺序不固定

特点:

  • ✅ 可以边跑边处理结果,不用等全部跑完
  • ✅ 可以看到实时进度
  • ❌ 打印顺序是“谁先完成谁先出”,不是任务列表的顺序

右边:asyncio.wait + 遍历 task.result() 版本(跑完再处理)

loop.run_until_complete(asyncio.wait(tasks))
for task in tasks:
    print(task.result())

执行流程:

  1. asyncio.wait(tasks) 会阻塞,直到所有任务都跑完
  2. 然后在普通代码里,遍历 tasks 列表,用 task.result() 拿每个任务的结果
  3. tasks 列表的创建顺序,依次打印结果

特点:

  • ✅ 打印顺序固定,和任务创建顺序一致
  • ❌ 必须等所有任务跑完,才能开始处理结果,看不到中间进度
  • ❌ 如果某个任务抛异常,task.result() 会直接抛出异常,需要额外处理

四、关键差异对比表

对比项 左边:as_completed + await 右边:wait + task.result()
处理时机 任务完成一个,处理一个 所有任务完成后,一次性处理
结果顺序 不固定(按完成先后) 固定(按任务创建顺序)
是否需要 async def ✅ 必须(因为用了 await ❌ 不需要(外面是普通代码)
异常处理 可以在循环里单独捕获 遍历 result() 时,异常会直接抛出
适用场景 边跑边处理、实时日志 批量汇总、统一处理结果

五、为什么你会觉得“在外层就不能打印”?

你困惑的点,其实是两个不同问题的叠加:

  1. await 不能写在外面
    这是 Python 的语法硬限制,所以“边跑边打印”的逻辑,必须包在 async def 里。

  2. 右边代码不是不能打印,只是打印方式不一样
    右边代码也能打印,只是:

    • 不能用 await,要用 task.result()
    • 必须等所有任务跑完才能打印
    • 顺序是固定的,不是实时的

六、用一句话总结

  • 左边:把“边跑边处理结果”的逻辑包进协程里,用 await 实时拿结果
  • 右边:先等所有任务跑完,再在普通代码里用 result() 批量拿结果

如果你愿意,我可以帮你把这两段代码改成完全等价的版本,加上日志,让你直观看到它们的执行顺序和打印差异。需要吗?

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐