【问题标题】:Cause python to exit if any thread has an exception如果任何线程发生异常,则导致 python 退出
【发布时间】:2018-04-05 02:17:27
【问题描述】:

我有一个 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)。我会在那里尝试一些解决方法。

虽然该示例显示了我创建的主线程和辅助线程,但我对可能正在运行其他人的启动线程的库的一般情况感兴趣。

【问题讨论】:

  • 你可以直接调用 sys.exit() 吗?
  • 嗯...我仍然希望将正常的堆栈跟踪打印到标准错误。我是否只使用一个巨大的顶级异常处理程序?

标签: multithreading python-3.x


【解决方案1】:

好吧,您可以简单地在线程中引发错误并让主线程处理并报告该错误。从那里你甚至可以终止程序。

例如在您的工作线程上:

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

(如果您有多个线程,我添加了一个额外的行来跟踪导致错误的线程,命名它们也是一个好习惯)

【讨论】:

  • 我已经在上面添加了一个示例,我会尝试看看是否可以将您的解决方案应用于此。
  • 所以,我查看了您的示例,从我所看到的情况来看,您还希望实现 join function 以确保线程同时退出。我使用的示例实现了多态性。
  • 如果我拥有所有线程创建机器,这将有效。但是如果我使用另一个库,例如 paho.mqttpyinotify,这并没有给我一个覆盖他们机器的机制。
  • 好吧,我研究了pyinotify并阅读了源代码。由于您没有具体说明您需要从该类中获得什么,我猜它是 ThreadedNotifier 类。查看源代码,您会看到它们有一个名为stop 的函数,它终止线程和run 函数。您可能想要尝试 catch 并将 self.stop 包含在 catch 块中。我只是看了一眼图书馆,但它似乎可行,所以我可能会弄错。
  • 要么覆盖该方法,要么只使用多态性对其进行编码。
【解决方案2】:

我的解决方案最终是上面的solution posted hereSIGKILL 解决方案之间的幸福婚姻。我在我的包中添加了以下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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-19
    • 2010-11-04
    • 2019-02-12
    • 1970-01-01
    • 2013-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多