asyncio CancelledError 和 KeyboardInterrupt [英] asyncio CancelledError and KeyboardInterrupt

查看:56
本文介绍了asyncio CancelledError 和 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆