asyncio CancelledError 和 KeyboardInterrupt [英] asyncio CancelledError and KeyboardInterrupt
问题描述
我正在尝试两种方法来阻止无限循环运行:
I'm trying 2 ways to stop an infinite loop from running:
- supervisor_1:以编程方式取消任务
- supervisor_2:使用 Ctrl+C 停止任务
- supervisor_1: task is canceled programatically
- supervisor_2: task is stopped with Ctrl+C
虽然 supervisor_2 在被中断时不会抛出任何错误,但我无法通过获取 Task 被破坏但它正在挂起!
来获取 supervisor_1.知道为什么吗?
While supervisor_2 does not throw any errors at when interrupted, I cannot get supervisor_1 from getting Task was destroyed but it is pending!
. Any idea why ?
代码如下:
import asyncio
import aioredis
from functools import partial
class Listener:
def __init__(self, redis_conn):
self.redis_conn = redis_conn
async def forever(self, loop_name):
counter = 0
try:
while True:
print('{}: {}'.format(loop_name, counter))
counter += 1
await asyncio.sleep(1)
except asyncio.CancelledError:
print('Task Cancelled')
self.redis_conn.close()
await self.redis_conn.wait_closed()
async def supervisor_1(redis_conn):
redis_conn = await redis_conn
l = Listener(redis_conn)
task = asyncio.ensure_future(
asyncio.gather(l.forever('loop_1'),
l.forever('loop_2')))
await asyncio.sleep(2)
task.cancel()
async def supervisor_2(redis_conn):
redis_conn = await redis_conn
l = Listener(redis_conn)
await asyncio.gather(l.forever('loop_1'),
l.forever('loop_2'))
if __name__ == '__main__':
redis_conn = aioredis.create_pool(('localhost', 5003), db=1)
loop = asyncio.get_event_loop()
run = partial(supervisor_2, redis_conn=redis_conn)
task = asyncio.ensure_future(run())
try:
loop.run_until_complete(task)
except KeyboardInterrupt:
print('Interruped !')
task.cancel()
loop.run_forever()
finally:
loop.close()
@update:
感谢@Gerasimov,这是一个解决问题的版本,但不知何故仍然不时在KeyboardInterrupt上引发错误:
Thanks to @Gerasimov, here is a version that fix the problem, but somehow still raise errors from time to time on KeyboardInterrupt:
async def supervisor(redis_conn):
redis_conn = await redis_conn
l = Listener(redis_conn)
task = asyncio.ensure_future(
asyncio.gather(l.forever('loop_1'),
l.forever('loop_2'))
)
await asyncio.sleep(10)
task.cancel()
with suppress(asyncio.CancelledError):
await task
async def kill_tasks():
pending = asyncio.Task.all_tasks()
for task in pending:
task.cancel()
with suppress(asyncio.CancelledError):
await task
和
if __name__ == '__main__':
redis_conn = aioredis.create_pool(('localhost', 5003), db=1)
loop = asyncio.get_event_loop()
run = partial(supervisor, redis_conn=redis_conn)
task = asyncio.ensure_future(run())
try:
loop.run_until_complete(task)
except KeyboardInterrupt:
print('Interruped !')
loop.run_until_complete(kill_tasks())
finally:
loop.close()
推荐答案
task.cancel()
本身并没有完成任务:它只是告诉任务 CancelledError
> 应该在里面升起并立即返回.您应该调用它并等待任务实际上会被取消(同时它会引发 CancelledError
).
task.cancel()
itself doesn't finish the task: it just says to task that CancelledError
should be raised inside it and returns immediately. You should call it and await while task would be actually cancelled (while it'll raise CancelledError
).
您也不应该在任务中抑制 CancelledError
.
You also shouldn't suppress CancelledError
inside task.
阅读这个答案,我试图展示处理任务的不同方式.例如取消某些任务并等待它取消,您可以执行以下操作:
Read this answer where I tried to show different ways of working with tasks. For example to cancel some task and await it cancelled you can do:
from contextlib import suppress
task = ... # remember, task doesn't suppress CancelledError itself
task.cancel() # returns immediately, we should await task raised CancelledError.
with suppress(asyncio.CancelledError):
await task # or loop.run_until_complete(task) if it happens after event loop stopped
# Now when we awaited for CancelledError and handled it,
# task is finally over and we can close event loop without warning.
这篇关于asyncio CancelledError 和 KeyboardInterrupt的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!