我有一个python3程序,它启动第二个线程(除了主线程)以异步处理某些事件。理想情况下,我的程序没有缺陷,并且从未有过未处理的异常。但事情发生了。当/如果有异常,我希望整个解释器以错误代码退出,就好像它是一个单独的线程一样。这可能吗?
现在,如果在生成的线程上发生异常,它会输出通常的错误信息,但不会退出。主线程一直在继续。
import threading
import time
def countdown(initial):
while True:
print(initial[0])
initial = initial[1:]
time.sleep(1)
if __name__ == '__main__':
helper = threading.Thread(target=countdown, args=['failsoon'])
helper.start()
time.sleep(0.5)
#countdown('THISWILLTAKELONGERTOFAILBECAUSEITSMOREDATA')
countdown('FAST')
countdown
最终无法从字符串中访问[0]
,因为它已被清空而导致IndexError: string index out of range
错误。目标是主要或帮助程序首先死亡,整个程序一共死亡,但堆栈跟踪信息仍然输出。
经过一番挖掘,我的想法是使用sys.excepthook
。我添加了以下内容:
def killAll(etype, value, tb):
print('KILL ALL')
traceback.print_exception(etype, value, tb)
os.kill(os.getpid(), signal.SIGKILL)
sys.excepthook = killAll
如果主线程是先死掉的线程,这是有效的。但在另一种情况下却没有。这似乎是一个已知问题(https://bugs.python.org/issue1230540)。我会尝试一些解决方法。
虽然示例显示了我创建的主线程和辅助线程,但我对一般情况感兴趣,我可能正在运行启动线程的其他人的库。
答案 0 :(得分:2)
好吧,你可以简单地在你的线程中引发一个错误并让主线程处理并报告该错误。从那里你甚至可以终止程序。
例如,在您的工作线程上:
try:
self.result = self.do_something_dangerous()
except Exception as e:
import sys
self.exc_info = sys.exc_info()
并在主线程上:
if self.exc_info:
raise self.exc_info[1].with_traceback(self.exc_info[2])
return self.result
为了给您一个更完整的图片,您的代码可能如下所示:
import threading
class ExcThread(threading.Thread):
def excRun(self):
pass
#Where your core program will run
def run(self):
self.exc = None
try:
# Possibly throws an exception
self.excRun()
except:
import sys
self.exc = sys.exc_info()
# Save details of the exception thrown
# DON'T rethrow,
# just complete the function such as storing
# variables or states as needed
def join(self):
threading.Thread.join(self)
if self.exc:
msg = "Thread '%s' threw an exception: %s" % (self.getName(), self.exc[1])
new_exc = Exception(msg)
raise new_exc.with_traceback(self.exc[2])
(我添加了一个额外的行来跟踪哪个线程导致错误,如果你有多个线程,那么命名它们也是一个好习惯)
答案 1 :(得分:1)
我的解决方案最终成为了solution posted here和SIGKILL
解决方案之间的幸福婚姻。我在我的包中添加了以下killall.py
子模块:
import threading
import sys
import traceback
import os
import signal
def sendKillSignal(etype, value, tb):
print('KILL ALL')
traceback.print_exception(etype, value, tb)
os.kill(os.getpid(), signal.SIGKILL)
original_init = threading.Thread.__init__
def patched_init(self, *args, **kwargs):
print("thread init'ed")
original_init(self, *args, **kwargs)
original_run = self.run
def patched_run(*args, **kw):
try:
original_run(*args, **kw)
except:
sys.excepthook(*sys.exc_info())
self.run = patched_run
def install():
sys.excepthook = sendKillSignal
threading.Thread.__init__ = patched_init
然后在启动任何其他线程之前立即运行install
(我自己的创建或其他导入的库)。