导致python在任何线程出现异常时退出

vsnjm48y  于 2023-04-22  发布在  Python
关注(0)|答案(4)|浏览(137)

我有一个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错误。目标是无论main还是helper首先死亡,整个程序都会死亡,但堆栈跟踪信息仍然输出。

尝试的解决方案

经过一番挖掘,我的想法是使用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)。我将尝试一些变通办法。
虽然这个例子显示了我创建的一个主线程和一个辅助线程,但我对运行其他人的库来启动线程的一般情况感兴趣。

vlju58qv

vlju58qv1#

你可以简单地在你的线程中抛出一个错误,让主线程处理并报告这个错误,然后你甚至可以终止程序。
例如,在您的工作线程上:

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])

(我额外添加了一行代码来记录是哪个线程导致了错误,以防你有多个线程,给它们命名也是一个很好的习惯)

mtb9vblg

mtb9vblg2#

我的解决方案最终是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

m2xkgtsf

m2xkgtsf3#

我只是想分享我的简单解决方案。
在我的例子中,我希望异常正常显示,但随后立即停止程序。我能够通过启动一个定时器线程来实现这一点,在引发异常之前有一个小的延迟来调用os._exit

import os
import threading

def raise_and_exit(args):
    threading.Timer(0.01, os._exit, args=(1,)).start()
    raise args[0]

threading.excepthook = raise_and_exit
fcy6dtqo

fcy6dtqo4#

Python 3.8添加了threading.excepthook,这使得可以更干净地处理它。
我写了一个包“unhandled_exit”来完成这个任务。它基本上是在默认处理程序之后添加os._exit(1)。这意味着在进程退出之前可以获得正常的回溯。
软件包在这里发布到pypi:https://pypi.org/project/unhandled_exit/
代码在这里:https://github.com/rfjakob/unhandled_exit/blob/master/unhandled_exit/__init__.py
用法很简单:

import unhandled_exit
unhandled_exit.activate()

相关问题