【问题标题】:How to gracefully terminate an asyncio script with Ctrl-C?如何使用 Ctrl-C 优雅地终止异步脚本?
【发布时间】:2018-07-11 18:52:42
【问题描述】:

我已经阅读了每一篇我能找到的关于如何优雅地处理一个异步事件循环被 Ctrl-C 终止的脚本的文章,并且我无法在不打印一个或多个的情况下让它们中的任何一个工作我这样做的追溯。答案几乎无处不在,我无法在这个小脚本中实现它们中的任何一个:

import asyncio
import datetime
import functools
import signal


async def display_date(loop):
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)


def stopper(signame, loop):
    print("Got %s, stopping..." % signame)
    loop.stop()


loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
    loop.add_signal_handler(getattr(signal, signame), functools.partial(stopper, signame, loop))

loop.run_until_complete(display_date(loop))
loop.close()

我想要让脚本退出而不打印 Ctrl-C(或通过kill 发送的 SIGTERM/SIGINT)之后的任何回溯。此代码打印RuntimeError: Event loop stopped before Future completed. 在我根据以前的答案尝试过的许多其他形式中,我得到了大量其他类型的异常类和错误消息,不知道如何修复它们。上面的代码现在是最小的,但我之前所做的一些尝试是不正确的,而且没有一个是正确的。

如果您能够修改脚本以使其优雅地终止,我们将非常感谢您解释为什么您的操作方式是正确方式。

【问题讨论】:

    标签: python signals python-asyncio sigint


    【解决方案1】:

    在事件循环运行时停止它永远不会有效。

    在这里,您需要按下 Ctrl-C,以向 Python 表明您希望自己处理它,而不是显示默认的堆栈跟踪。这可以通过经典的 try/except 来完成:

    coro = display_date(loop)
    try:
        loop.run_until_complete(coro)
    except KeyboardInterrupt:
        print("Received exit, exiting")
    

    而且,对于您的用例,就是这样! 对于更真实的程序,您可能需要清理一些资源。另见Graceful shutdown of asyncio coroutines

    【讨论】:

    • 谢谢。作为对任何其他读者的澄清,我不得不删除整个信号处理程序 - 添加 for 循环,并且最后的 loop.close() 也是不必要的。
    • 我无法相信我浪费了多少时间试图让add_signal_handler(SIGINT) 工作;感谢您指出一条实际可行的更简单的路径。
    • 我还必须在异常中添加RuntimeError,因为我收到了RuntimeError: Event loop stopped before Future completed.
    【解决方案2】:

    使用signal 处理程序:

    import asyncio
    from signal import SIGINT, SIGTERM
    
    async def main_coro():
        try:
            await awaitable()
        except asyncio.CancelledError:
            do_cleanup()
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
        main_task = asyncio.ensure_future(main_coro())
        for signal in [SIGINT, SIGTERM]:
            loop.add_signal_handler(signal, main_task.cancel)
        try:
            loop.run_until_complete(main_task)
        finally:
            loop.close()
    

    【讨论】:

    • 如果你只写协程而不是主代码,你可以使用asyncio.get_running_loop()asyncio.current_task()分别获取循环和任务,这样我在协程中添加了信号处理程序(允许使用asyncio.run() 调用它)
    • asyncio.add_signal_handler() 在 win32 上引发 NotImplementedError (python 3.7.3)。
    猜你喜欢
    • 2014-08-17
    • 1970-01-01
    • 2018-01-10
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    • 2015-06-07
    • 1970-01-01
    相关资源
    最近更新 更多