为什么异步函数的输出在调用前就被打印?
虽然我的代码看起来差不多,但异步函数的表现却不一样,我不明白为什么。
我的代码是:
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
在第一个情况下,为什么 func2
在 func1
之前打印出来,尽管 func2
是晚开始的呢?
1 个回答
不管你怎么把 time
传给 func2
,关键在于你是怎么启动 func1
和 func2
的:
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 没有被等待”。
文档中提到:“重要提示:保存这个函数结果的引用,以避免任务在执行中消失。事件循环只保留对任务的弱引用。一个没有在其他地方被引用的任务可能会随时被垃圾回收,甚至在它完成之前。为了可靠的‘火并忘’的后台任务,应该把它们收集在一个集合中。”