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

JK *_*iho 8 python signals sigint python-asyncio

我已经阅读了所有可以找到的有关如何优雅地处理带有asyncio事件循环并以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()
Run Code Online (Sandbox Code Playgroud)

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

如果您能够修改脚本以使其优雅地终止,那么不胜感激地向您解释为什么这样做是正确的方法。

Man*_*d3r 16

使用信号处理程序:

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()
Run Code Online (Sandbox Code Playgroud)

  • win32 上的 `asyncio.add_signal_handler()` 会引发 NotImplementedError (python 3.7.3)。 (5认同)

Art*_*hur 7

在事件循环运行时停止它永远是无效的。

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

coro = display_date(loop)
try:
    loop.run_until_complete(coro)
except KeyboardInterrupt:
    print("Received exit, exiting")
Run Code Online (Sandbox Code Playgroud)

而且,就您的用例而言,就是这样!对于更真实的程序,您可能需要清理一些资源。另请参见Asyncio协程的正常关闭