Asyncio.sleep 导致脚本立即结束

问题描述 投票:0回答:2

在下面的简单

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。

python python-3.x asynchronous python-asyncio
2个回答
1
投票

此行为与调用

asyncio.sleep
无关,而是与创建任务而不执行其他操作的预期行为有关。 任务将在 asyncio 循环中并行运行,而仅使用协程和等待表达式的其他代码可以被视为以线性模式运行 - 然而,因为它们“不碍事” - 让我们称之为“可见的执行路径”,它们也不会阻止该流程。

在这种情况下,您的程序只是到达

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
 

0
投票

接受的答案对我来说还不够,因为它没有解释为什么观察到的行为会发生。

似乎在使用

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()

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

© www.soinside.com 2019 - 2024. All rights reserved.