如何优雅地使用Ctrl-C终止asyncio脚本?

37

我阅读了所有关于如何优雅地处理带有asyncio事件循环的脚本在使用Ctrl-C被终止的文章,但我无法使它们在不打印一个或多个tracebacks的情况下正常工作。答案几乎都是五花八门的,我也没能将它们中的任何一种实现到这个小脚本中:

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.。我尝试了许多其他形式,基于以前的答案,但是得到了各种其他类型的异常类和错误消息,不知道如何修复它们。现在上面的代码很简短,但我之前尝试的一些方法则完全不同,而且都不正确。
如果您能修改脚本使其优雅地终止,那么解释为什么您的方式是正确的将不胜感激。
2个回答

30

使用信号处理程序

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() 调用)。 - Scott Stevens
4
在win32上,asyncio.add_signal_handler()会引发NotImplementedError(Python 3.7.3)。 - JimB

25

在事件循环正在运行时停止它永远都不是有效的。

在这里,你需要捕获 Ctrl-C 信号,告诉 Python 你想自己处理它而不是显示默认的堆栈跟踪信息。可以使用经典的 try/except 来完成:

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

而对于您的使用情况,就是这样! 如果需要更真实的程序,您可能需要清理一些资源。另请参阅异步协程的优雅关闭


1
谢谢。为了澄清给其他读者,我必须删除整个信号处理程序添加的“for”循环,并且最后的“loop.close()”也是不必要的。 - JK Laiho
我简直不敢相信我浪费了多少时间尝试让 add_signal_handler(SIGINT) 起作用;感谢您指出了一条更简单实际可行的路径。 - Mike C
我还需要在异常上添加RuntimeError,因为我收到了 RuntimeError: Event loop stopped before Future completed. 的错误。 - Leonardo Rick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接