在下面的简单
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。
此行为与调用
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
接受的答案对我来说还不够,因为它没有解释为什么观察到的行为会发生。
似乎在使用
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()
很明显,当“主要”任务完成时,所有其他任务都会被取消,循环也会停止。