为啥 PyQt 有时会在退出时崩溃?

Posted

技术标签:

【中文标题】为啥 PyQt 有时会在退出时崩溃?【英文标题】:Why does PyQt sometimes crash on exit?为什么 PyQt 有时会在退出时崩溃? 【发布时间】:2020-03-25 23:15:27 【问题描述】:

下面给定的代码显示一个QMainWindow 和4 个QGraphicsView 以在其中使用鼠标进行绘制。它按预期工作,但在关闭它时,控制台中会出现以下错误消息:

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

代码有什么问题?


main.py

​​>
import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPathItem
from PyQt5.QtGui import QPainterPath, QPen
from PyQt5.QtCore import Qt
from PyQt5.uic import loadUi

# Based on code from https://***.com/a/44248794/7481773

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        loadUi("mainwindow.ui", self)

        self.verticalLayout_top_left.addWidget(GraphicsView())
        self.verticalLayout_top_right.addWidget(GraphicsView())
        self.verticalLayout_bottom_left.addWidget(GraphicsView())
        self.verticalLayout_bottom_right.addWidget(GraphicsView())


class GraphicsView(QGraphicsView):
    def __init__(self):
        super().__init__()
        self.start = None
        self.end = None

        self.setScene(QGraphicsScene())
        self.path = QPainterPath()
        self.item = GraphicsPathItem()
        self.scene().addItem(self.item)

        self.contents_rect = self.contentsRect()
        self.setSceneRect(0, 0, self.contents_rect.width(), self.contents_rect.height())
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def mousePressEvent(self, event):
        self.start = self.mapToScene(event.pos())
        self.path.moveTo(self.start)

    def mouseMoveEvent(self, event):
        self.end = self.mapToScene(event.pos())
        self.path.lineTo(self.end)
        self.start = self.end
        self.item.setPath(self.path)


class GraphicsPathItem(QGraphicsPathItem):
    def __init__(self):
        super().__init__()
        pen = QPen()
        pen.setColor(Qt.black)
        pen.setWidth(5)
        self.setPen(pen)


def main():
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    app.exec_()


if __name__ == "__main__":
    main()

主窗口.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="1" column="0">
     <widget class="QPushButton" name="pushButton_left">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item row="0" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_top_left"/>
    </item>
    <item row="0" column="1">
     <layout class="QVBoxLayout" name="verticalLayout_top_right"/>
    </item>
    <item row="1" column="1">
     <widget class="QPushButton" name="pushButton_right">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item row="2" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_bottom_left"/>
    </item>
    <item row="2" column="1">
     <layout class="QVBoxLayout" name="verticalLayout_bottom_right"/>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

【问题讨论】:

标题中带有“代码工作正常”的问题确实不应该包含“代码有什么问题?”这句话。反之亦然。 我改了标题。 【参考方案1】:

这是由一个长期存在的问题引起的,该问题将在即将发布的版本(可能是 PyQt-5.14)中修复。如果您使用的是 PyQt-5.13.1,则可以使用以下临时 API 测试修复:

from PyQt5.QtCore import pyqt5_enable_new_onexit_scheme

pyqt5_enable_new_onexit_scheme(True)

如果没有报告问题,它最终将成为默认行为(因此无需显式启用它)。此处记录了导致该问题的根本问题:

PyQt5 文档:Crashes On Exit。

本质上,Python 的垃圾收集方案会以不可预知的顺序删除对象,这有时会导致 Qt 尝试删除不再存在的对象(导致崩溃)。所以问题中的例子也可以这样固定:

def main():
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    app.exec_()
    # ensure correct deletion order
    del main_window, app

上述即将发生的变化将意味着不再需要这种清理代码。

【讨论】:

很好的解释! 这个问题有更新吗?我有一个案例,添加对 app.processEvents() 的调用开始导致这些崩溃。 @ekhumoro 概述的修复并没有修复它。我在 Mac 上运行 PyQt 5.12,因为这是 conda 上可用的最新版本。我很快会尝试在 Linux 上测试它。 @RayOsborn 更新在我回答的第一部分中指定。如前所述,第二部分中的“修复”仅适用于问题中的示例。如果你有不同的问题,你应该问一个新问题(minimal reproducible example),因为没有其他人会在这里看到你的 cmets。 感谢您的回答。我真的很感谢人们在这里回答问题的时间——这些年来它对我帮助很大,但如果这个“最小可重复的例子”真的成为一种规则,那么我认为这是一个倒退。我的应用程序有数万行代码,所以问题可能是由于它们互锁的方式而出现的。一个简单的例子并不总是可能的。人们经常来这里是有经验的人给他们建议,不一定能明确地解决问题。请不要阻止此类问题。 顺便说一句,PyQt 邮件列表上的某个人刚刚向我展示了如何通过使用 QApplication allWidgets 函数显式关闭所有***小部件来解决问题,因此删除主窗口,然后毕竟不需要QApplication。他遇到过与我类似的问题,并且能够在没有 MRE 的情况下指导我找到解决方案。【参考方案2】:

导致问题的原因是,即使您完成运行 app.exec_() 应用程序仍会释放需要访问 QApplication 实例的资源,但在您的情况下,“app”在执行此操作之前已通过使内部函数被删除Qt 访问未保留的内存。

考虑到上述情况,一个可能的解决方案是通过创建一个全局变量来扩展“app”的范围,使其在执行主函数后不会被删除。

# ...
app = None


def main():
    global app
    app = QApplication(sys.argv)
    # ...

【讨论】:

这是我第一次在 PyQt 中遇到这个错误。为什么在这段代码中过早地删除了“app”? @Atalanttore 因为“app”是一个局部变量 @Atalanttore 也许在这些情况下没有创建需要时间才能发布的资源,因此分析问题所在将是分析 Qt 的私有 API,但这需要太多时间,所以我的解决方案是我通常使用的解决方法。 有趣。我无法在 Linux 上重现这种行为(我不得不说,使用相当旧的版本,Python 3.4 和 PyQt 5.7.1)。 sys.exit(app.exec()) 会改变结果吗? @musicamante 我可以在 Python3.7/Python3.8 中使用 PyQt5 5.13.2 重现 Linux(Arch Linux)中的问题

以上是关于为啥 PyQt 有时会在退出时崩溃?的主要内容,如果未能解决你的问题,请参考以下文章

Windows 上的 PyQt4 应用程序在退出时崩溃

为啥在 macOS 上使用 QThread 时 PyQt 应用程序崩溃或挂起?

为啥程序退出时全局或静态对象会导致崩溃?

PyQt - 当我切换到第三个窗口时系统崩溃

为啥这个简单的 NSWindow 创建代码会在 ARC 下关闭时触发自动释放池崩溃?

为啥我在退出我的 Activity 时会崩溃?