关闭应用程序时多次出现错误“QObject::startTimer: QTimer can only be used with threads started with QThread”
Posted
技术标签:
【中文标题】关闭应用程序时多次出现错误“QObject::startTimer: QTimer can only be used with threads started with QThread”【英文标题】:Error "QObject::startTimer: QTimer can only be used with threads started with QThread" many times when closing application 【发布时间】:2015-01-23 17:26:47 【问题描述】:我知道这已经被问过很多次了。我阅读了所有这些主题,我的情况似乎有所不同。遇到此问题的其他所有人都有一些我认为我已经排除的直接原因,例如:
在没有运行事件循环的情况下启动计时器 从创建定时器的线程以外的线程启动/停止定时器 未能设置小部件的父属性,导致销毁顺序出现问题下面我有一个演示问题的最小代码示例。请注意,我没有启动任何线程或计时器。我还设置了每个小部件的父级。如果我删除图形小部件,问题就会消失,所以有人很想责怪 pyQtGraph,但是,如果我包含绘图小部件但排除所有空白选项卡(即除了 tabCatchaTiger 之外的每个选项卡),问题也会消失,并且似乎证明 pyQtGraph 是正确的。
版本:
Windows 7 Python 2.7.8 Wing IDE 5.0.9-1 PyQt 4.11.1 PyQwt 5.2.1 PyQtGraph 0.9.8测试用例:
from PyQt4 import Qt, QtGui, QtCore
import PyQt4.Qwt5 as Qwt
import pyqtgraph as pg
pg.functions.USE_WEAVE = False # Lets pyqtgraph plot without gcc
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
# GUI for visualizing data from database
class crashyGUI(QtGui.QWidget) :
def __init__(self) :
# Make the window
QtGui.QWidget.__init__(self)
self.resize(700, QtGui.QDesktopWidget().screenGeometry(self).height()*.85)
self.setWindowTitle('Data Visualization')
# Create tab interface
tabWidget = QtGui.QTabWidget(self)
# define the tab objects
self.tabEeny = QtGui.QWidget(tabWidget)
self.tabMeeny = QtGui.QWidget(tabWidget)
self.tabMiney = QtGui.QWidget(tabWidget)
self.tabMoe = QtGui.QWidget(tabWidget)
self.tabCatchaTiger = QtGui.QWidget(tabWidget)
self.tabByThe = QtGui.QWidget(tabWidget)
self.tabToe = QtGui.QWidget(tabWidget)
# Initialize the tab objects
self.initTabCatchaTiger()
###########################################
############### Main Layout ###############
###########################################
tabWidget.addTab(self.tabEeny, 'Eeny')
tabWidget.addTab(self.tabMeeny, 'Meeny')
tabWidget.addTab(self.tabMiney, 'Miney')
tabWidget.addTab(self.tabMoe, 'Moe')
tabWidget.addTab(self.tabCatchaTiger, 'Catch a Tiger')
tabWidget.addTab(self.tabByThe, 'By The')
tabWidget.addTab(self.tabToe, 'Toe')
self.mainLayout = QtGui.QVBoxLayout(self)
self.mainLayout.addWidget(tabWidget)
self.setLayout(self.mainLayout)
def initTabCatchaTiger(self):
###########################################
############# ADC Capture Tab #############
###########################################
# define tab layout
grid = QtGui.QGridLayout(self.tabCatchaTiger)
# create copy of adc plot and add to row 3 of the grid
self.catchaTigerPlot1 = pg.PlotWidget(name = 'Catch a Tiger 1', parent = self.tabCatchaTiger)
self.catchaTigerPlot1.setTitle('Catch a Tiger 1')
grid.addWidget(self.catchaTigerPlot1, 2, 0, 1, 8)
self.catchaTigerPlot2 = pg.PlotWidget(name = 'Catch a Tiger 2', parent = self.tabCatchaTiger)
self.catchaTigerPlot2.setTitle('Catch a Tiger 2')
grid.addWidget(self.catchaTigerPlot2, 3, 0, 1, 8)
# set layout for tab
self.tabCatchaTiger.setLayout(grid)
def closeEvent(self, event) :
pass
def main() :
# open a QApplication and dialog() GUI
app = QtGui.QApplication([])
windowCrashy = crashyGUI()
windowCrashy.show()
app.exec_()
main()
【问题讨论】:
我不太明白你的结论。仅当 pyqtgraph 选项卡存在但未显示时才会出现此问题。所以看起来绘图小部件的初始化存在故障(我不会说错误,因为它只会发出警告消息)。 【参考方案1】:示例中似乎有两个密切相关的问题。
第一个导致 Qt 在退出时打印 QObject::startTimer: QTimer can only be used with threads started with QThread
消息。
第二个(可能不会影响所有用户)导致 Qt 打印QPixmap: Must construct a QApplication before a QPaintDevice
,然后在退出时转储核心。
这两个问题都是由python在退出时以不可预测的顺序删除对象引起的。
在示例中,可以通过将以下行添加到***窗口的__init__
来修复第二个问题:
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
除非QApplication.setQuitOnLastWindowClosed已更改为False
,否则这将确保应用程序在正确的时间退出,并且Qt有机会在python垃圾之前自动删除顶层窗口的所有子项-收集器开始工作。
但是,要完全成功,所有相关对象必须在父子层次结构中链接在一起。示例代码尽可能做到这一点,但在 PlotWidget
类的初始化中似乎有一些关键地方没有完成。
特别是,没有什么可以确保PlotWidget
的中心项在创建时具有父集。如果代码的相关部分改成这样:
class PlotWidget(GraphicsView):
...
def __init__(self, parent=None, background='default', **kargs):
GraphicsView.__init__(self, parent, background=background)
...
self.plotItem = PlotItem(**kargs)
# make sure the item gets a parent
self.plotItem.setParent(self)
self.setCentralItem(self.plotItem)
然后QTimer
消息的第一个问题也消失了。
【讨论】:
使用WA_DeleteOnClose
在我的情况下有效。谢谢:)
我已经构建了一个不会发生此错误的 pyqt5 gui 应用程序工具。然后在一个类似的项目中,我完全实现了我在上一个项目中所做的,这个错误发生了。太奇怪了【参考方案2】:
这里有一个更好的答案:
您允许在 python 退出之前收集QApplication
。这会导致两个不同的问题:
QTimer 错误消息是由 pyqtgraph 在 QApplication 被销毁后试图跟踪其 ViewBoxes 引起的。
崩溃似乎是 Qt / PyQt 所固有的。以下以相同的方式崩溃:
from PyQt4 import Qt, QtGui, QtCore
def main() :
app = QtGui.QApplication([])
x = QtGui.QGraphicsView()
s = QtGui.QGraphicsScene()
x.setScene(s)
x.show()
app.exec_()
main()
您可以通过在主函数中添加global app
或在模块级别创建QApplication
来修复它。
【讨论】:
在这种特定情况下,这是由于您的示例中的错误造成的。场景应将视图设置为其父级。 我也不认为global app
修复可能是可靠的。当我尝试使用 OP 的原始示例时,它仍然会在退出时转储核心。一个对我有用的解决方案是在主窗口的__init__
中添加self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
,尽管我不知道这是否更可靠。但是应该指出,这些“修复”仅在创建绘图小部件时才需要 - 否则原始示例对我来说很好。
如果场景没有父级,为什么会发生崩溃? (例如,PySide 不会崩溃)
也许我应该说“潜在的疏忽”而不是“错误”。 AFAIUI,Python 不保证对象在退出时会按特定顺序删除。另一方面,当 Qt 删除一个对象时,它总是尝试删除它的所有子对象,这通常会确保对象以正确的顺序被删除。当 Qt 取得对象的所有权时,这一点尤其重要,因为您最终可能会尝试两次删除该对象(这将导致崩溃)。
至于 PyQt 和 PySide 之间的区别 - 我对两者的实现都不太了解,无法对此发表评论。根据我之前的评论,我几乎总是发现这样的问题可以通过“良好的内务管理”来处理。我想这可能意味着我有时最终会解决 PySide/PyQt 中的真正错误——也许当前的例子就是其中之一!但无论如何,这肯定是一个难以处理的领域,尤其是如果(像我一样)你没有太多 C++ 经验。【参考方案3】:
尝试在 __init__ 块中写这个:
self.setAttribute(Qt.WA_DeleteOnClose)
【讨论】:
【参考方案4】:就我个人而言,我不再花任何精力去追踪退出崩溃了——只要使用 pg.exit()
就可以了。
(但如果你碰巧在 pyqtgraph 中发现了一个错误,请不要犹豫在 github 上打开一个问题)
【讨论】:
这真是个蹩脚的 API。如果 PySide/PyQt 中那些所谓的错误是真实的,为什么不报告它们? 其中一些已被报告。其他的只是因为需要花费太多精力来追踪而被删除(例如,将 10 万行的应用程序提炼到重现退出崩溃所需的最少行是不值得的)。 好的,但是在公共 API 的文档中指责 PySide/PyQt 的理由是什么?当然,pyqtgraph 的作者有责任在他们开始指责他人之前排除他们自己代码中的错误? 一些错误被追溯到 PyQt/PySide 并被报告。 一些的错误被追溯到 pyqtgraph 并被修复。 大部分的错误根本没有被追踪(所以你是对的,pyqtgraph 应该包含在该列表中)。 是的,调用 pg.exit() 似乎可以解决问题。所以问题可能出在 pyqtgraph 中,但是如果存在退出函数,似乎 pyqtgraph 故意让应用程序负责告诉它何时关闭,那么这真的是一个错误吗?【参考方案5】:我也遇到过这种情况,在我的情况下,它是由在应用程序的 aboutToQuit
-Signal 上调用 deleteLater()
引起的,如下所示:
def closeEvent(self, *args, **kwargs):
self.deleteLater()
if __name__ == "__main__":
application = QtWidgets.QApplication(sys.argv)
window = testApplication()
# Handle application exit
application.aboutToQuit.connect(window.closeEvent)
# System exit
sys.exit(application.exec_())
删除整个窗口的 deleteLater 似乎可以解决它。
【讨论】:
以上是关于关闭应用程序时多次出现错误“QObject::startTimer: QTimer can only be used with threads started with QThread”的主要内容,如果未能解决你的问题,请参考以下文章
每秒进行多次调用时,Google API Client for Google BigQuery Call 的授权失败并出现错误 403