Skip to content

Python 处理 Qt 异常注意点

Qt 退出但是 Python 没法记录错误

之前一个项目需要点击一个按钮后做一些图像处理。由于处理时间很长,所以用了 QThread,即点击按钮后这个 QThread 就会 start,但当时遇到一个问题就是:QThread 总是莫名其妙退出,用 try catch 也抓不到错误。

搞了半天终于弄懂:因为我点击按钮后,这个方法就结束了。即点击按钮后我初始化了一个 QThread 变量,调用其 start 方法之后,就退出了。此时相当于主线程已经退出,在主线程初始化的变量会被回收,那很显然 QThread 就运行不了。

所以最后方法是要始终保持一个 QThread 变量即可:

class Test(...):
  def __init__(self):
      # 需要保持一个变量,否则 on_click 方法结束后,线程变量会被回收
      self.thread = QThread()
      self.button.connect(self.on_click)

  def on_click(self):
      # 获取一些参数
      config = self.get_config()
      # 传入到线程中,其中 prepare 是自己定义的方法
      self.thread.prepare(config)
      # 启动线程
      self.thread.start()

或者大胆一点,直接就是在点击按钮后再初始化,但改成是成员变量:

class Test(...):
  def __init__(self):
      self.button.connect(self.on_click)

  def on_click(self):
      # ...
      # 之前是 thread = QThread(),现在直接绑定到 self,所以结束之后不会被回收
      self.thread = QThread()
      self.thread.start()

Qt 错误在主线程中获取

有一次遇到一个问题:

QObject::setParent: Cannot set parent, new parent is in a different thread

这个错误曾经让我 DEBUG 了半天,这个绝对是因为在 QThread 里面新建了一个 GUI。

我当时信誓旦旦觉得我没这样做。但最后发现:我是在 GUI 里面设置了一个 QThread,我在 main.py 中有一个全局异常处理函数,每次异常后就进入这个函数了。

def show_error_dialog_in_main_thread():
    msg_box = QMessageBox()
    msg_box.setWindowFlags(msg_box.windowFlags() | Qt.WindowStaysOnTopHint)
    msg_box.setIcon(QMessageBox.Critical)
    msg_box.setWindowTitle("未处理的错误")
    msg_box.setText(f'发生错误,请检查 {os.path.dirname(__file__)} 中的 LOG.txt 文件')
    msg_box.exec_()

    # 错误框关闭后退出
    sys.exit(1)

def global_exception_handler(exctype, value, tb):
    print(f'{time.strftime("%Y-%m-%d %H:%M:%S")}\n')
    traceback.print_exception(exctype, value, tb, file=sys.stdout)

    # 出错时,会新建一个 QMessageBox
    show_error_dialog_in_main_thread()

但是如果是 QThread 里面报错,同样会到这个函数,此时进入到这个函数是在 QThread 这个线程了,此时新建 GUI 就会有问题。

处理方式就是需要 GUI 中连接 QThread 的错误信号,即让它回到主线程上来。

class CpatureWin(QWidget, window_2.Ui_Form):
    def __init__(self):
        # ...

        self.capture_thread = captureThread.CaptureThread()
        # ! 连接错误信号,不这样做全局捕获异常会有问题
        self.capture_thread.error_occurred.connect(self.handle_thread_error)

    # QThread 出错时,直接向上报错
    def handle_thread_error(self, error_msg):
        raise Exception(error_msg)

class CpatureThread(QThread):
    error_occurred = pyqtSignal(str)

Comments