在Windows中,asyncio循环的add_signal_handler()是什么?

11

我目前正在将一个Python项目从Linux移植到Windows(使用Anaconda Python 3.6)。一切都工作得很完美,只是我无法让asyncio循环优雅地退出。

在Linux中,我正在执行以下操作:

class GracefulExit(SystemExit):
    code = 1

def raise_graceful_exit():
    raise GracefulExit()

loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, raise_graceful_exit)
loop.add_signal_handler(signal.SIGTERM, raise_graceful_exit)

try:
    loop.run_forever()
except GracefulExit:
    pass

shutdown()

在Windows中,不幸的是我遇到了add_signal_handlerNotImplementedError错误。如果没有这个功能,当然我就永远没有机会对程序进行干净的关闭。

有什么解决方法吗?谢谢。


请参见问题23057 - Eryk Sun
3个回答

8

Windows没有信号。

如果进程通过TerminateProcess API被终止,您将没有任何机会进行清理(就像'kill -9'一样,直接结束进程)。

但是Windows有两种方法可以发出信号告诉您的代码应该退出,一种是针对控制台程序(例如python.exe),另一种是针对GUI程序(pythonw.exe)。

Python自动处理控制台问题并引发KeyboardInterrupt异常,但您可以使用控制台控制处理程序API(https://learn.microsoft.com/en-us/windows/console/console-control-handlers)将自己的代码挂钩到该处理程序中,但这可能过于复杂。只需在事件循环上设置适当的异常处理程序即可。

对于GUI进程,Windows会向您发送窗口消息,例如WM_QUIT或其他各种消息,如果用户注销、系统进入节能模式等,您也可以使用ctypes或win32包或任何许多Python GUI库来处理这些消息。


谢谢。这正是我担心的。我想最简单的方法就是为Windows添加一个简单的GUI界面... - The_Fallen
1
@The_Fallen,你尝试过在问题23057中添加定期任务的建议吗?@schlenk,C运行时有一个控制台控制处理程序,它会引发SIGINT以响应Ctrl+C和其他事件的SIGBREAKKeyboardInterrupt来自Python的默认处理程序,但您可以替换它。对于asyncio,signal.set_wakeup_fd已更新以支持Windows上的套接字,但由于某种原因,在Windows事件循环中实现信号处理程序的补丁从未最终完成和合并。我不使用asyncio,所以我没有理由接手这个任务。 - Eryk Sun
没有,我刚刚使用pyqt添加了一个简单的窗口。工作得很好,在关闭窗口时循环也能够结束。不管怎样,还是谢谢你。 - The_Fallen

7

实际上,您可以在Python脚本中实现一种跨平台的Python信号处理程序,它适用于Unix / Linux和Windows。Python有一个标准的signal库,因此您可以像这样实现:

import asyncio
import signal


class GracefulExit(SystemExit):
    code = 1


def raise_graceful_exit(*args):
    loop.stop()
    print("Gracefully shutdown")
    raise GracefulExit()


def do_something():
    while True:
        pass


loop = asyncio.get_event_loop()
signal.signal(signal.SIGINT, raise_graceful_exit)
signal.signal(signal.SIGTERM, raise_graceful_exit)

try:
    loop.run_forever(do_something())
except GracefulExit:
    pass
finally:
    loop.close()

由于上述平台差异,它在Windows和Linux上的表现并不完全相同,但对于大多数情况,在两个平台上都能正常工作。


loop.run_forever不需要任何参数。(您是不是想使用run_until_complete?) 此外,未完成的任务会发生什么情况?假设我有一个任务,在取消时需要终止线程或子进程? - user48956
与使用signal.signal()注册的信号处理程序不同,使用loop.add_signal_handler()注册的回调函数允许与事件循环进行交互。我对此的所有含义都不确定,但我认为这个文档片段值得在这个答案中突出显示。https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.add_signal_handler - N1ngu

2
另一个多平台优雅退出的版本。
import asyncio
import signal

async def main():
    try:
        i=0
        while i<10:
            print('<Your app is running>')
            await asyncio.sleep(1)
            i +=1
    except asyncio.CancelledError: 
        for i in range(3):
            print('<Your app is shutting down...>')
            await asyncio.sleep(1)

if __name__ == '__main__':

    class GracefulExit(SystemExit):
        code = 1


    def raise_graceful_exit(*args):
        tasks = asyncio.all_tasks(loop=loop)
        for t in tasks:
            t.cancel()

        loop.stop()
        print("Gracefully shutdown")
        raise GracefulExit()


    def do_something():
        while True:
            pass


    loop = asyncio.get_event_loop()
    signal.signal(signal.SIGINT,  raise_graceful_exit)
    signal.signal(signal.SIGTERM, raise_graceful_exit)
    task = loop.create_task(main())
    try:
        loop.run_until_complete(task)
    except GracefulExit:
        print('Got signal: SIGINT, shutting down.')
    if 1:
        tasks = asyncio.all_tasks(loop=loop)
        for t in tasks:
            t.cancel()
        group = asyncio.gather(*tasks, return_exceptions=True)
        loop.run_until_complete(group)
        loop.close()



我认为直接从信号处理程序中调用loop.stop()是不可能的,至少在有任何外部事件进入时是不可能的。但是,通过从信号处理程序中调用loop.create_task(shutdown()),然后shutdown()调用loop.stop(),我已经让它工作了。此外,我猜想loop.run_until_complete(loop.shutdown_asyncgens())应该是取消所有待处理任务的更简单的方法? - Radagast
缺少 loop=asyncio.gather(*tasks, loop=loop, return_exceptions=True) - user48956

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