Hello StackExchange社区,
首先,你们都对我很有帮助,非常感谢。第一次提问:
我正在编写一个PyQt GUI应用程序,我发现它在Windows系统上崩溃了,它也让我在家里的机器上有一个段错误,而它在工作的那个上工作(两个linux mint 17)。经过一些研究,我意识到我可能已经创建了一个线程不安全的GUI,因为我有几个对象调用彼此的方法。
From another stackoverflow question: GUI小部件只能从主线程访问,这意味着调用QApplication.exec()的线程。从任何其他线程访问GUI小部件 - 你在调用self.parent()时所做的是未定义的行为,在你的情况下,这意味着崩溃。
From Qt docs:虽然QObject是可重入的,但GUI类,尤其是QWidget及其所有子类,都不是可重入的。它们只能在主线程中使用。如前所述,还必须从该线程调用QCoreApplication :: exec()。
所以最后,我认为我应该只使用信号槽系统。
接下来,我在示例代码中重新创建此线程不安全行为时遇到问题。 Qt文档说QObjects存在于不同的线程中。这意味着,以下Qt应用程序应该是线程不安全的(如果我没有正确的话)。
from PyQt4 import QtGui
import sys
class TestWidget(QtGui.QWidget):
def __init__(self,string):
super(TestWidget,self).__init__()
self.button = QtGui.QPushButton(string,parent=self)
self.button.clicked.connect(self.buttonClicked)
# just to check, and yes, lives in it's own thread
print self.thread()
def buttonClicked(self):
# the seemingly problematic line
self.parent().parent().statusBar().showMessage(self.button.text())
pass
pass
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
Layout = QtGui.QHBoxLayout()
for string in ['foo','bar']:
Layout.addWidget(TestWidget(string))
CentralWidget = QtGui.QWidget(self)
CentralWidget.setLayout(Layout)
self.setCentralWidget(CentralWidget)
self.statusBar()
self.show()
pass
pass
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
M = MainWindow()
sys.exit(app.exec_())
但它在我的我的手机上运行也很好。
感谢您帮助我解决这个问题......
答案 0 :(得分:3)
这是正确的吗?
是的,你应该只使用信号槽系统来进行q-objects之间的交互。 这就是它的意思。
这仅用于函数调用,还是可以操作某些对象的字段 从运行时的其他对象以线程安全的方式?
我有一个可以从多个其他对象访问的选项对象......
如果这里的对象你的意思是Q对象:
您的options
对象应该支持信号槽机制,您可以实现此目的
从options
中获取QObject
。
class Options(QtCore.QObject):
optionUpdated = QtCore.pyqtSignal(object)
def __init__(self):
self.__options = {
'option_1': None
}
def get_option(self, option):
return self.__options.get(option)
def set_option(self, option, value):
self.__options[option] = value
self.optionUpdated.emit(self)
然后使用此选项的所有窗口小部件/对象都应该有一个连接到此信号的插槽。
一个简单的例子:
options = Options()
some_widget = SomeWidget()
options.optionUpdated.connect(some_widget.options_updated) // Is like you implement the observer pattern, right?
<强>为什么呢?这实际上是线程不安全而且可能崩溃,但它不是吗?
thread-unsafe
并不代表&#34;崩溃得到保证&#34;但是&#34;这可能会崩溃&#34;或者&#34;这种情况很可能会崩溃&#34;。
来自pyqt API doc QObject.thread:
返回对象所在的线程。
<强>勘误强>
正如ekumoro指出的那样,我已经重新检查了我之前关于每个对象离开的位置,并且......我错了!
QObject.thread
将为每个对象返回不同的 QThread实例,但QThread
实际上并不是一个线程只是操作系统提供的那些线程的包装器。
因此,代码在不同的线程中有几个对象没有问题。
为了简单起见,我已经修改了用于演示的代码:
from PyQt4 import QtGui
import sys
class TestWidget(QtGui.QWidget):
def __init__(self,string):
super(TestWidget,self).__init__()
# just to check, and yes, lives in it's own thread
print("TestWidget thread: {}".format(self.thread()))
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
print("Window thread: {}".format(self.thread()))
Layout = QtGui.QHBoxLayout()
for string in ['foo','bar']:
Layout.addWidget(TestWidget(string))
self.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
M = MainWindow()
sys.exit(app.exec_())
并且是的,这打印:
Window thread: <PyQt4.QtCore.QThread object at 0x00000000025C1048>
TestWidget thread: <PyQt4.QtCore.QThread object at 0x00000000025C4168>
TestWidget thread: <PyQt4.QtCore.QThread object at 0x00000000025C41F8>
演示每个控件都存在于自己的线程中。
现在,你有信号槽机制来处理这个&#34;线程安全&#34;,任何其他方法都不是线程安全的。
答案 1 :(得分:2)
您的问题的答案:
GUI小部件只能从主线程(运行的线程)访问
QApplication.exec_()
)。默认情况下,信号和插槽是线程安全的
Qt 4
任何导致直接 Qt图形对象操作的调用来自另一个线程而不是主线程不线程安全=&gt;会崩溃
你的问题代码中没有涉及线程(线程在哪里?), 不同的QObject存在于不同的线程中。也许你遇到的崩溃无关 有线程?
答案 2 :(得分:1)
作为一些注释的后续内容,下面是一个测试脚本,它显示了如何检查代码执行的线程:
from PyQt4 import QtCore, QtGui
class Worker(QtCore.QObject):
threadInfo = QtCore.pyqtSignal(object, object)
@QtCore.pyqtSlot()
def emitInfo(self):
self.threadInfo.emit(self.objectName(), QtCore.QThread.currentThreadId())
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtGui.QPushButton('Test', self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
self.thread = QtCore.QThread(self)
self.worker1 = Worker()
self.worker1.setObjectName('Worker1')
self.worker1.moveToThread(self.thread)
self.worker1.threadInfo.connect(self.handleShowThreads)
self.button.clicked.connect(self.worker1.emitInfo)
self.worker2 = Worker()
self.worker2.setObjectName('Worker2')
self.worker2.threadInfo.connect(self.handleShowThreads)
self.button.clicked.connect(self.worker2.emitInfo)
self.thread.start()
def handleShowThreads(self, name, identifier):
print('Main: %s' % QtCore.QThread.currentThreadId())
print('%s: %s\n' % (name, identifier))
def closeEvent(self, event):
self.thread.quit()
self.thread.wait()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())