python - Python警报信号,从外部超时当前线程并处理异常?
问题描述
众所周知,python 中的信号只能在主线程内工作,这是我关于这个主题的小片段:
import signal
from threading import Timer
from time import sleep
class timeout:
def __init__(self, seconds=1, error_message='Timeout error'):
self.seconds = seconds
self.error_message = error_message
def handle_timeout(self, signum, frame):
raise TimeoutError(self.error_message)
def __enter__(self):
signal.signal(signal.SIGALRM, self.handle_timeout)
signal.alarm(self.seconds)
def __exit__(self, type, value, traceback):
signal.alarm(0)
def main():
try:
with timeout(seconds=2) :
#do_something
sleep(3)
print ("don't come here after 3 seconds")
except Exception as e:
print ("catch here",str(e))
print ("continue ...")
t = Timer(0.0, main)
t.start()
现在,为了强制它工作,我signal.signal
在线程外部放置了一个带有钩子的动态函数。
class timeout:
def __init__(self, seconds=1, error_message='Timeout error'):
self.seconds = seconds
self.error_message = error_message
def handle_timeout(self):
raise TimeoutError(self.error_message)
def __enter__(self):
#fluid.error = self.error_message
#fluid.__call__ = self.handle_timeout
signal.alarm(self.seconds)
def __exit__(self, type, value, traceback):
signal.alarm(0)
class fluid:
error = 'Orpheline exception'
def __init__(self,signum,frame):
self.signum = signum
self.frame = frame
def __call__(self):
try:
raise TimeoutError(self.error)
except Exception as e:
print ("catch now", str(e))
signal.signal(signal.SIGALRM, lambda x,y:fluid(x,y)())
t = Timer(0.0, main)
try:
t.start()
except Exception as e:
print ("catch there",str(e))
使用猴子补丁解决这个问题会产生以下结果:
- 如果我取消注释 this:
fluid.error = self.error_message
,则会在类中捕获异常。 - 如果我取消注释这个:
fluid.__call__ = self.handle_timeout
,两个主处理程序都没有捕获异常,程序退出!
唯一对我有用的解决方案是提供一个新的标志值skipvalue
,用于检查此并行线程中是否存在异常:
class timeout:
def __init__(self, seconds=1, error_message='Timeout error'):
self.seconds = seconds
self.error_message = error_message
self.skipvalue = False
self.SKIP = lambda : self.skipvalue
def handle_timeout(self):
raise TimeoutError(self.error_message)
def timeitout(self):
#print('not caught ',self.error_message)
self.skipvalue = True
def __enter__(self):
fluid.error = self.error_message
#fluid.__call__ = self.handle_timeout
fluid.__call__ = self.timeitout
signal.alarm(self.seconds)
return self.SKIP
def __exit__(self, type, value, traceback):
signal.alarm(0)
def main():
try:
with timeout(seconds=2,error_message="Some message") as e :
#do_something
sleep(3)
if e():
raise Timeout(fluid.error)
print ("don't come here after 3 seconds")
except Exception as e:
print ("catch here",str(e))
print ("continue ...")
t = Timer(0.0, main)
t.start()
以上使用睡眠功能最多需要 3 秒,在任意循环中,我需要在每个执行周期检查新值。
我的问题:
- 有没有更优雅和内置的方法来实现这个目标,而不用在代码中填充不必要的变量或类或分叉子进程?
解决方案
Appearent 没有办法用计时器来做到这一点,但使用带有系统跟踪的线程似乎是半可能的
import sys
import trace
import threading
import time
import signal
class thread_with_trace(threading.Thread):
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, target=kwargs["target"],args=(self,))
self.killed = False
self.ex_handler = kwargs["handler"]
def start(self):
self.__run_backup = self.run
self.run = self.__run
threading.Thread.start(self)
def __run(self):
sys.settrace(self.globaltrace)
self.__run_backup()
self.run = self.__run_backup
def globaltrace(self, frame, event, arg):
if event == 'call':
return self.localtrace
else:
return None
def localtrace(self, frame, event, arg):
if self.killed:
if event == 'line':
raise SystemExit()
return self.localtrace
def kill(self):
self.killed = True
raise self.ex_handler
class fluid:
def __init__(self,signum,frame):
self.signum = signum
self.frame = frame
# do whatever according to signal id
def __call__(self):
pass
signal.signal(signal.SIGALRM, lambda x,y:fluid(x,y)())
class timeout:
def __init__(self, thread=lambda: None, terminatefun=lambda: None, seconds=10):
self.seconds = seconds
self.thisthread = thread
self.terminatefun = terminatefun
def handle_timeout(self):
try:
self.thisthread.kill()
except Exception as e:
print(str(e))
self.terminatefun()
def __enter__(self):
fluid.__call__ = self.handle_timeout
signal.alarm(self.seconds)
def __exit__(self, type, value, traceback):
signal.alarm(0)
def stopit():
print("I should be here after two seconds")
def func(t):
with timeout(thread=t, terminatefun=stopit ,seconds=2):
while True:
time.sleep(0.1)
print("I'm running")
t1 = thread_with_trace(target=func,args=[],handler=TimeoutError("Ran out of time"))
t1.start()
它几乎符合要求,因为SystemExit()
在最后一次超时到期后停止线程(在这种情况下为 0.1 秒)
推荐阅读
- javascript - 直接打开表单视图窗口而不是日历 Odoo 10 上的摘要窗口
- datadog - 在 Datadog 的仪表板中显示服务的版本
- ruby - 是否有任何真实世界的字体在 CFF 表的顶部字典中指定编码?
- regex - semver 正则表达式比这个更便携或额外覆盖?
- android - Firebase 在父级观察/查询并忽略其中一个子级
- dart - 如何在 Flutter 中移除 AppBar 前导图标周围的额外填充
- python-2.7 - 面对 StaleElementReferenceException:消息:元素不再是有效的问题。请检查是否有出路
- python-3.x - 使用 selenium 在 python3 中执行 javascript 代码时出错
- java - 您可以从 jsp 文件中 <% %> 块中的 servlet 获取会话属性吗?
- windows - 如何在保留其所有配置文件的同时在同级目录中运行可执行文件?