为什么异步函数的输出在调用前就被打印?

1 投票
1 回答
46 浏览
提问于 2025-04-13 01:27

虽然我的代码看起来差不多,但异步函数的表现却不一样,我不明白为什么。

我的代码是:

import asyncio

async def func1():
    await asyncio.sleep(1)
    print("func1")

async def func2():
    await asyncio.sleep(time)
    print("func2")

async def func3():
    await asyncio.sleep(3)
    print("func3")

async def main():
    asyncio.create_task(func1())
    await func2()
    await func3()

asyncio.run(main())

如果我把 time = 1,那么输出是:

func2

func1

func3

但是如果 time = 2,那么输出是:

func1

func2

func3

在第一个情况下,为什么 func2func1 之前打印出来,尽管 func2 是晚开始的呢?

1 个回答

1

不管你怎么把 time 传给 func2,关键在于你是怎么启动 func1func2 的:

async def main():
    asyncio.create_task(func1())
    await func2()
    ...

你先创建了一个任务,然后立刻等待 func2 的执行。接着,当 func2 进入休眠状态时,func1 就有机会开始执行,也会进入休眠。

如果 func2 发现 time == 1,它会在一秒后醒来并打印,然后 func1 也会在稍后醒来。

如果 func2 发现 time == 2,它会在两秒后才醒来,而这时 func1 已经醒来了。

你问的“为什么在第一种情况下,func2 打印在 func1 之前,尽管 func2 启动得晚?”——你认为 func2 “启动得晚”,但其实并不是。它是在创建 func1 的任务之前就被等待了。你并没有明确等待这个任务,而你的程序可能在这个任务完成之前就结束了,但因为你有 func3 在那里等待得更久,所以几乎可以保证它会完成。

你会发现,当你这样做时,顺序并没有改变:

async def main():
    await func1()
    await func2()
    ...

因为这样两个异步函数会按顺序被等待,并且在提供相同的 sleep() 时间时,会按顺序完成。

同样的,使用:

async def main():
    t = asyncio.create_task(func1())
    await t
    await func2()
    ...

顺序会如你所预期的那样,因为这个任务在启动和等待 func2() 之前被明确等待了。

或者当然,你可以直接:

async def main():
    await asyncio.create_task(func1())
    await func2()
    ...

一个好的编辑器或IDE会提醒你,如果你没有等待 create_task。比如 PyCharm 会警告:“协程 create_task 没有被等待”。

文档中提到:“重要提示:保存这个函数结果的引用,以避免任务在执行中消失。事件循环只保留对任务的弱引用。一个没有在其他地方被引用的任务可能会随时被垃圾回收,甚至在它完成之前。为了可靠的‘火并忘’的后台任务,应该把它们收集在一个集合中。”

撰写回答