python-3.x Asyncio.sleep导致脚本立即结束

w3nuxt5m  于 4个月前  发布在  Python
关注(0)|答案(2)|浏览(72)

在下面的asyncio Python程序中,bar_loop应该是连续运行的,循环之间有1秒的延迟。
当我们简单地

async def bar_loop(self):
        while True:
            print('bar')

字符串
但是,当我们添加一个asyncio.sleep(1)时,循环将结束,而不是循环。

async def bar_loop(self):
        while True:
            print('bar')
            await asyncio.sleep(1)


为什么asyncio.sleep()会导致bar_loop立即退出?我们怎么能让它以1秒的延迟循环?

完整示例:

import asyncio
from typing import Optional

class Foo:
    def __init__(self):
        self.bar_loop_task: Optional[asyncio.Task] = None
        
    async def start(self):
        self.bar_loop_task = asyncio.create_task(self.bar_loop())

    async def stop(self):
        if self.bar_loop_task is not None:
            self.bar_loop_task.cancel()

    async def bar_loop(self):
        while True:
            print('bar')
            await asyncio.sleep(1)

if __name__ == '__main__':
    try:
        foo = Foo()
        asyncio.run(foo.start())
    except KeyboardInterrupt:
        asyncio.run(foo.stop())


在Ubuntu 20.04上使用Python 3.9.5。

fsi0uk1n

fsi0uk1n1#

这种行为与调用asyncio.sleep无关,而是与创建任务而不做其他任何事情的预期行为有关。任务将在thetcio循环中并行运行,而其他只使用协程和await表达式的代码可以被认为是以线性模式运行的-然而,由于它们是“不碍事的”-让我们称之为“可见的执行路径”,也无法阻止这种流动
在这种情况下,你的程序只是到达了start方法的末尾,没有任何东西被“等待”,所以start循环只是完成了它的执行。
如果你没有显式的代码可以与bar_loop并行运行,那就等待任务的执行。将你的start方法改为:

async def start(self):
    self.bar_loop_task = asyncio.create_task(self.bar_loop())
    try:
        await self.bar_loop_task
    except XXX:
        # handle excptions that might have taken place inside the task

字符串

fsi0uk1n

fsi0uk1n2#

公认的答案对我来说是不够的,因为它没有解释为什么观察到的行为会发生。
似乎在使用asyncio.run时,所有任务都会在主“入口点”协程完成后被取消。
所以这段代码:

import asyncio
import time

task = None

async def _generate():
    print("Starting generation")
    for x in range(10):
        print("generating...")
        await asyncio.sleep(1)
        print("it never gets printed")

async def main():
    print("executing main")
    global task
    task = asyncio.create_task(_generate())
    print(task)
    print("main executed")

asyncio.run(main(), debug=True)
print(task)

字符串
印刷品:

<Task pending name='Task-2' coro=<_generate() running at /home/mateusz/PROJECTS/Project3 - PyTango/issue-current/how_does_it_work.py:6> created at /home/mateusz/miniconda3/lib/python3.9/asyncio/tasks.py:361>
main executed
Starting generation
generating...
<Task cancelled name='Task-2' coro=<_generate() done, defined at /home/mateusz/PROJECTS/Project3 - PyTango/issue-current/how_does_it_work.py:6> created at /home/mateusz/miniconda3/lib/python3.9/asyncio/tasks.py:361>


发生这种情况的原因是asyncio.run的实现,它在表面下使用loop.run_until_complete。看看实现(文件asyncio/base_events.py):

def run_until_complete(self, future):
    # ...
    future.add_done_callback(_run_until_complete_cb)
    try:
        self.run_forever()
    # ...
   
def _run_until_complete_cb(fut):
    # ...
    futures._get_loop(fut).stop()


所以很明显,当“main”任务完成时,所有其他任务都会被取消,循环也会停止。

相关问题