关闭无限异步生成器

时间:2018-11-27 23:40:46

标签: python python-asyncio

可再现的错误

我试图在在线REPL here中重现该错误。但是,它与我的真实代码(在REPL中执行async for response in position_stream()而不是for position in count())中的实现和行为并不完全相同。

有关我的实际实施的更多详细信息

我在某处定义一个协程,如下所示:

async def position(self):
    request = telemetry_pb2.SubscribePositionRequest()
    position_stream = self._stub.SubscribePosition(request)

    try:
        async for response in position_stream:
            yield Position.translate_from_rpc(response)
    finally:
        position_stream.cancel()

其中position_stream是无限的(或可能持续很长时间)。我从这样的示例代码中使用它:

async def print_altitude():
    async for position in drone.telemetry.position():
        print(f"Altitude: {position.relative_altitude_m}")

print_altitude()在循环中使用:

asyncio.ensure_future(print_altitude())
asyncio.get_event_loop().run_forever()

那很好。现在,在某个时候,我想关闭来自调用方的流。我以为我可以只运行asyncio.ensure_future(loop.shutdown_asyncgens())并等待靠近我的finally被叫,但那没有发生。

相反,我收到有关未检索到的异常的警告:

Task exception was never retrieved
future: <Task finished coro=<print_altitude() done, defined at [...]

为什么会这样,如何使所有异步生成器实际上都关闭(并运行其finally子句)?

1 个答案:

答案 0 :(得分:1)

首先,如果您stop进行循环,则协程中没有一个将有机会正常关闭。调用close基本上意味着不可逆转地破坏循环。

如果您不关心正在运行的任务会发生什么,只需将它们全部cancel都将停止,这也会停止异步生成器:

import asyncio
from contextlib import suppress


async def position_stream():
    while True:
        await asyncio.sleep(1)
        yield 0

async def print_position():
    async for position in position_stream():
        print(f'position: {position}')

async def cleanup_awaiter():
    await asyncio.sleep(3)
    print('cleanup!')

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        asyncio.ensure_future(print_position())
        asyncio.ensure_future(print_position())
        loop.run_until_complete(cleanup_awaiter())
        # get all running tasks:
        tasks = asyncio.gather(*asyncio.Task.all_tasks())
        # schedule throwing CancelledError into the them:
        tasks.cancel()
        # allow them to process the exception and be cancelled:
        with suppress(asyncio.CancelledError):
            loop.run_until_complete(tasks)
    finally:
        print('closing loop')
        loop.close()