← 返回题目列表

Python 中直接 await 和 asyncio.create_task 有什么区别?

高频 中等 第 10 / 27 题 更新于 2026/07/27
awaitcreate_taskTask调度

简化版

直接 await coro() 会等待这个协程完成,当前协程在这里暂停;asyncio.create_task(coro()) 会把协程包装成 Task 并立即交给事件循环调度,让它可以和当前协程并发执行。想顺序执行用 await,想启动后台并发任务用 create_task,但创建的 Task 要妥善等待或处理异常。

详细版

顺序执行:

async def main():
    a = await fetch_a()
    b = await fetch_b()

这里 fetch_b() 要等 fetch_a() 完成后才开始。

并发执行:

async def main():
    task_a = asyncio.create_task(fetch_a())
    task_b = asyncio.create_task(fetch_b())

    a = await task_a
    b = await task_b

两个任务会尽早被事件循环调度,整体耗时通常接近较慢的那个任务。

注意点:

  • create_task() 需要运行中的事件循环;
  • Task 创建后就可能开始执行;
  • 不要创建 Task 后完全不管,否则异常可能丢失或收尾不可控;
  • 需要结构化并发时可以考虑 asyncio.TaskGroup

面试回答重点:await 是等待一个 awaitable,create_task 是把协程安排成并发任务。

完整版教学

一、直接 await 更像顺序流程

看这段代码:

async def main():
    user = await get_user()
    orders = await get_orders(user.id)

这里订单依赖用户 ID,所以顺序是合理的。await get_user() 没完成前,后面的代码不会继续。

直接 await 的特点是控制流清楚,适合有依赖关系的步骤。

二、create_task 会把协程交给事件循环调度

如果两个请求互不依赖:

async def main():
    profile_task = asyncio.create_task(get_profile())
    message_task = asyncio.create_task(get_messages())

    profile = await profile_task
    messages = await message_task

create_task 把协程包装成 Task。Task 是事件循环可调度的单位,它会尽快开始执行。这样两个 I/O 等待可以重叠。

三、只创建协程对象不会自动并发

很多人以为这样就是并发:

coro1 = get_profile()
coro2 = get_messages()

profile = await coro1
messages = await coro2

这通常仍然是先等待 coro1,再等待 coro2。创建协程对象不等于调度执行。要让它们并发,应该创建 Task 或使用 gather

四、不要让 Task 失控

这类代码有风险:

asyncio.create_task(send_log())

如果你不保存 Task,也不等待它:

  • 任务异常可能只被事件循环记录;
  • 程序退出时任务可能没执行完;
  • 任务生命周期不可控;
  • 测试中可能出现偶发失败。

更稳妥的做法是把任务纳入管理:

task = asyncio.create_task(send_log())
...
await task

或者使用 TaskGroup。

五、create_task 与后台任务

有些任务确实想后台执行,例如异步上报、预加载、并发请求。但后台不代表不用管。

你至少要考虑:

  • 任务失败怎么记录;
  • 程序关闭时怎么取消或等待;
  • 是否需要超时;
  • 是否会无限增长;
  • 是否会访问已经关闭的资源。

异步系统里最可怕的不是“没有并发”,而是“任务满天飞,谁也不知道它们什么时候失败”。

六、常见误区与追问

写法是否立即并发调度适合场景
await coro()不会和后续代码并发,先等它完成有明确顺序依赖
task = create_task(coro())注册到事件循环,后续可并发推进多个独立任务
只写 coro = coro_func()不运行只是创建协程对象
await task等已有任务结果收尾、拿返回值、传播异常
  • 误区:调用 async 函数就会开始执行。 调用只创建协程对象,没人 await 或调度它时,函数体不会真正跑起来。
  • 误区:create_task 后可以不管它。 失控后台任务可能吞掉异常、泄漏资源、在请求结束后继续访问无效上下文。
  • 误区:await 一定会并发。 如果连续 await a(); await b(),两个操作是顺序完成,除非先创建任务再一起等待。
  • 追问:什么时候直接 await 更好? 后一步依赖前一步结果,或只想表达顺序流程时,直接 await 更清楚。
  • 追问:什么时候 create_task 更好? 多个相互独立的 I/O 操作需要重叠等待时间时,先创建任务再统一等待。
  • 追问:后台任务异常怎么处理? 应保留 task 引用、设置回调、集中 await,或放入结构化并发管理,避免异常无人观察。

记忆钩子:await 是“现在等它”,create_task 是“先交给循环跑,稍后再管它”。

七、加强记忆

await 解决“我现在要等结果”,create_task 解决“我先把它安排出去和别人一起跑”。协程对象只是待执行的东西,Task 才是交给事件循环调度的任务。创建 Task 后要负责它的结果、异常和生命周期,不能随手丢掉。