为啥 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 有时会在退出时崩溃?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 macOS 上使用 QThread 时 PyQt 应用程序崩溃或挂起?