如何从异步信号处理程序中捕获自定义异常?
How to catch custom exception from signal handler in asyncio?
使用 asyncio
.
时,我在捕获信号处理程序回调抛出的自定义异常时遇到问题
如果我从下面的 do_io()
中抛出 ShutdownApp
,我可以在 run_app()
中正确地捕捉到它。但是,当 handle_sig()
引发异常时,我似乎无法捕获它。
Minimal, Reproducible Example 使用 Python 3.8.5:
测试
import asyncio
from functools import partial
import os
import signal
from signal import Signals
class ShutdownApp(BaseException):
pass
os.environ["PYTHONASYNCIODEBUG"] = "1"
class App:
def __init__(self):
self.loop = asyncio.get_event_loop()
def _add_signal_handler(self, signal, handler):
self.loop.add_signal_handler(signal, handler, signal)
def setup_signals(self) -> None:
self._add_signal_handler(signal.SIGINT, self.handle_sig)
def handle_sig(self, signum):
print(f"\npid: {os.getpid()}, Received signal: {Signals(signum).name}, raising error for exit")
raise ShutdownApp("Exiting")
async def do_io(self):
print("io start. Press Ctrl+C now.")
await asyncio.sleep(5)
print("io end")
def run_app(self):
print("Starting Program")
try:
self.loop.run_until_complete(self.do_io())
except ShutdownApp as e:
print("ShutdownApp caught:", e)
# TODO: do other shutdown related items
except:
print("Other error")
finally:
self.loop.close()
if __name__ == "__main__":
my_app = App()
my_app.setup_signals()
my_app.run_app()
print("Finished")
在异步调试模式下按 CTRL+C
(对于 SIGINT
)后的输出:
(env_aiohttp) anav@anav-pc:~/Downloads/test$ python test_asyncio_signal.py
Starting Program
io start. Press Ctrl+C now.
^C
pid: 20359, Received signal: SIGINT, raising error for exit
Exception in callback App.handle_sig(<Signals.SIGINT: 2>)
handle: <Handle App.handle_sig(<Signals.SIGINT: 2>) created at /home/anav/miniconda3/envs/env_aiohttp/lib/python3.8/asyncio/unix_events.py:99>
source_traceback: Object created at (most recent call last):
File "test_asyncio_signal.py", line 50, in <module>
my_app.setup_signals()
File "test_asyncio_signal.py", line 25, in setup_signals
self._add_signal_handler(signal.SIGINT, self.handle_sig)
File "test_asyncio_signal.py", line 22, in _add_signal_handler
self.loop.add_signal_handler(signal, handler, signal)
File "/home/anav/miniconda3/envs/env_aiohttp/lib/python3.8/asyncio/unix_events.py", line 99, in add_signal_handler
handle = events.Handle(callback, args, self, None)
Traceback (most recent call last):
File "/home/anav/miniconda3/envs/env_aiohttp/lib/python3.8/asyncio/events.py", line 81, in _run
self._context.run(self._callback, *self._args)
File "test_asyncio_signal.py", line 31, in handle_sig
raise ShutdownApp("Exiting")
ShutdownApp: Exiting
io end
Finished
预期输出:
Starting Program
io start. Press Ctrl+C now.
^C
pid: 20359, Received signal: SIGINT, raising error for exit
ShutdownApp caught: Exiting
io end
Finished
是否可以从 asyncio
中的信号处理程序引发自定义异常?如果是这样,我该如何正确catch/except呢?
如果我从下面的 do_io() 中抛出 ShutdownApp,我可以在 run_app() 中正确地捕获它。但是,当 handle_sig() 引发异常时,我似乎无法捕获它。
对上面给出的查询的响应
自定义异常实现:
class RecipeNotValidError(Exception):
def __init__(self):
self.message = "Your recipe is not valid"
try:
raise RecipeNotValidError
except RecipeNotValidError as e:
print(e.message)
在方法 handle_sig 中,添加 try 和 except 块。另外,您可以在自定义异常中自定义消息。
def handle_sig(self, signum):
try:
print(f"\npid: {os.getpid()}, Received signal: {Signals(signum).name}, raising
error for exit")
raise ShutdownApp("Exiting")
except ShutdownApp as e:
print(e.message)
回应您的第二个查询:
是否可以从 asyncio 中的信号处理程序引发自定义异常?如果可以,我该如何正确catch/except呢?
In-built Asyncio 中的错误处理。如需更多文档,请访问 https://docs.python.org/3/library/asyncio-exceptions.html
import asyncio
async def f():
try:
while True: await asyncio.sleep(0)
except asyncio.CancelledError:
print('I was cancelled!')
else:
return 111
handle_sig
是一个 回调 ,所以它直接在事件循环之外运行,它的异常只是通过全局挂钩报告给用户。如果您希望在那里引发的异常在程序的其他地方被捕获,您需要使用 future 将异常从 handle_sig
转移到您希望它被注意到的地方。
要捕获 top-level 处的异常,您可能想引入另一种方法,我们称之为 async_main()
,等待 either self.do_io()
或 previously-created 未来完成:
def __init__(self):
self.loop = asyncio.get_event_loop()
self.done_future = self.loop.create_future()
async def async_main(self):
# wait for do_io or done_future, whatever happens first
io_task = asyncio.create_task(self.do_io())
await asyncio.wait([self.done_future, io_task],
return_when=asyncio.FIRST_COMPLETED)
if self.done_future.done():
io_task.cancel()
await self.done_future # propagate the exception, if raised
else:
self.done_future.cancel()
要从 handle_sig
内部引发异常,您只需 set the exception 未来对象:
def handle_sig(self, signum):
print(f"\npid: {os.getpid()}, Received signal: {Signals(signum).name}, raising error for exit")
self.done_future.set_exception(ShutdownApp("Exiting"))
最后,您修改 run_app
以将 self.async_main()
传递给 run_until_complete
,一切就绪:
$ python3 x.py
Starting Program
io start. Press Ctrl+C now.
^C
pid: 2069230, Received signal: SIGINT, raising error for exit
ShutdownApp caught: Exiting
Finished
最后,请注意可靠地捕获键盘中断是一个 notoriously tricky undertaking 并且上面的代码可能无法涵盖所有极端情况。
使用 asyncio
.
如果我从下面的 do_io()
中抛出 ShutdownApp
,我可以在 run_app()
中正确地捕捉到它。但是,当 handle_sig()
引发异常时,我似乎无法捕获它。
Minimal, Reproducible Example 使用 Python 3.8.5:
测试import asyncio
from functools import partial
import os
import signal
from signal import Signals
class ShutdownApp(BaseException):
pass
os.environ["PYTHONASYNCIODEBUG"] = "1"
class App:
def __init__(self):
self.loop = asyncio.get_event_loop()
def _add_signal_handler(self, signal, handler):
self.loop.add_signal_handler(signal, handler, signal)
def setup_signals(self) -> None:
self._add_signal_handler(signal.SIGINT, self.handle_sig)
def handle_sig(self, signum):
print(f"\npid: {os.getpid()}, Received signal: {Signals(signum).name}, raising error for exit")
raise ShutdownApp("Exiting")
async def do_io(self):
print("io start. Press Ctrl+C now.")
await asyncio.sleep(5)
print("io end")
def run_app(self):
print("Starting Program")
try:
self.loop.run_until_complete(self.do_io())
except ShutdownApp as e:
print("ShutdownApp caught:", e)
# TODO: do other shutdown related items
except:
print("Other error")
finally:
self.loop.close()
if __name__ == "__main__":
my_app = App()
my_app.setup_signals()
my_app.run_app()
print("Finished")
在异步调试模式下按 CTRL+C
(对于 SIGINT
)后的输出:
(env_aiohttp) anav@anav-pc:~/Downloads/test$ python test_asyncio_signal.py
Starting Program
io start. Press Ctrl+C now.
^C
pid: 20359, Received signal: SIGINT, raising error for exit
Exception in callback App.handle_sig(<Signals.SIGINT: 2>)
handle: <Handle App.handle_sig(<Signals.SIGINT: 2>) created at /home/anav/miniconda3/envs/env_aiohttp/lib/python3.8/asyncio/unix_events.py:99>
source_traceback: Object created at (most recent call last):
File "test_asyncio_signal.py", line 50, in <module>
my_app.setup_signals()
File "test_asyncio_signal.py", line 25, in setup_signals
self._add_signal_handler(signal.SIGINT, self.handle_sig)
File "test_asyncio_signal.py", line 22, in _add_signal_handler
self.loop.add_signal_handler(signal, handler, signal)
File "/home/anav/miniconda3/envs/env_aiohttp/lib/python3.8/asyncio/unix_events.py", line 99, in add_signal_handler
handle = events.Handle(callback, args, self, None)
Traceback (most recent call last):
File "/home/anav/miniconda3/envs/env_aiohttp/lib/python3.8/asyncio/events.py", line 81, in _run
self._context.run(self._callback, *self._args)
File "test_asyncio_signal.py", line 31, in handle_sig
raise ShutdownApp("Exiting")
ShutdownApp: Exiting
io end
Finished
预期输出:
Starting Program
io start. Press Ctrl+C now.
^C
pid: 20359, Received signal: SIGINT, raising error for exit
ShutdownApp caught: Exiting
io end
Finished
是否可以从 asyncio
中的信号处理程序引发自定义异常?如果是这样,我该如何正确catch/except呢?
如果我从下面的 do_io() 中抛出 ShutdownApp,我可以在 run_app() 中正确地捕获它。但是,当 handle_sig() 引发异常时,我似乎无法捕获它。
对上面给出的查询的响应
自定义异常实现:
class RecipeNotValidError(Exception):
def __init__(self):
self.message = "Your recipe is not valid"
try:
raise RecipeNotValidError
except RecipeNotValidError as e:
print(e.message)
在方法 handle_sig 中,添加 try 和 except 块。另外,您可以在自定义异常中自定义消息。
def handle_sig(self, signum):
try:
print(f"\npid: {os.getpid()}, Received signal: {Signals(signum).name}, raising
error for exit")
raise ShutdownApp("Exiting")
except ShutdownApp as e:
print(e.message)
回应您的第二个查询: 是否可以从 asyncio 中的信号处理程序引发自定义异常?如果可以,我该如何正确catch/except呢?
In-built Asyncio 中的错误处理。如需更多文档,请访问 https://docs.python.org/3/library/asyncio-exceptions.html
import asyncio
async def f():
try:
while True: await asyncio.sleep(0)
except asyncio.CancelledError:
print('I was cancelled!')
else:
return 111
handle_sig
是一个 回调 ,所以它直接在事件循环之外运行,它的异常只是通过全局挂钩报告给用户。如果您希望在那里引发的异常在程序的其他地方被捕获,您需要使用 future 将异常从 handle_sig
转移到您希望它被注意到的地方。
要捕获 top-level 处的异常,您可能想引入另一种方法,我们称之为 async_main()
,等待 either self.do_io()
或 previously-created 未来完成:
def __init__(self):
self.loop = asyncio.get_event_loop()
self.done_future = self.loop.create_future()
async def async_main(self):
# wait for do_io or done_future, whatever happens first
io_task = asyncio.create_task(self.do_io())
await asyncio.wait([self.done_future, io_task],
return_when=asyncio.FIRST_COMPLETED)
if self.done_future.done():
io_task.cancel()
await self.done_future # propagate the exception, if raised
else:
self.done_future.cancel()
要从 handle_sig
内部引发异常,您只需 set the exception 未来对象:
def handle_sig(self, signum):
print(f"\npid: {os.getpid()}, Received signal: {Signals(signum).name}, raising error for exit")
self.done_future.set_exception(ShutdownApp("Exiting"))
最后,您修改 run_app
以将 self.async_main()
传递给 run_until_complete
,一切就绪:
$ python3 x.py
Starting Program
io start. Press Ctrl+C now.
^C
pid: 2069230, Received signal: SIGINT, raising error for exit
ShutdownApp caught: Exiting
Finished
最后,请注意可靠地捕获键盘中断是一个 notoriously tricky undertaking 并且上面的代码可能无法涵盖所有极端情况。