Python PyQt崩溃与线程安全
你好,斯塔克交换社区 首先,你们对我帮助很大,非常感谢。第一次提问: 我目前正在编写一个PyQt GUI应用程序,我发现它在windows系统上崩溃,而且它在家里的机器上给我一个segfault,而它在工作的机器上工作(都是linux mint 17)。经过一些研究,我意识到我可能已经创建了一个线程不安全的GUI,因为我有几个相互调用方法的对象 :GUI小部件只能从主线程访问,这意味着调用QApplication.exec()的线程。从任何其他线程访问GUI小部件——您对self.parent()的调用是未定义的行为,在您的情况下,这意味着崩溃 :尽管QObject是可重入的,但GUI类,尤其是QWidget及其所有子类,是不可重入的。它们只能从主线程使用。如前所述,还必须从该线程调用QCoreApplication::exec() 所以最后,我想我应该只使用信号槽系统来实现这一点Python PyQt崩溃与线程安全,python,multithreading,qt,pyqt4,Python,Multithreading,Qt,Pyqt4,你好,斯塔克交换社区 首先,你们对我帮助很大,非常感谢。第一次提问: 我目前正在编写一个PyQt GUI应用程序,我发现它在windows系统上崩溃,而且它在家里的机器上给我一个segfault,而它在工作的机器上工作(都是linux mint 17)。经过一些研究,我意识到我可能已经创建了一个线程不安全的GUI,因为我有几个相互调用方法的对象 :GUI小部件只能从主线程访问,这意味着调用QApplication.exec()的线程。从任何其他线程访问GUI小部件——您对self.parent(
谢谢你帮我解决这个问题……这是正确的吗? 是的,您应该只使用信号槽系统来与q对象进行交互。 这就是它的本意 这只是函数调用所需,还是我可以操纵某些对象的字段 在运行时以线程安全的方式从其他对象删除? 我有一个可从多个其他对象访问的选项对象 如果这里所说的对象是指Q对象: 您的
选项
对象应该支持信号槽机制,您可以实现这一点
从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?
为什么?这是否真的是线程不安全并且可能崩溃,但事实并非如此?
线程不安全
并不意味着“保证崩溃”,而是“这可能会崩溃”或“这很可能会崩溃”
从pyqt API文档:
返回对象所在的线程
勘误表
正如ekumoro所指出的,我已经重新检查了我之前关于每个物体在不同线程中离开的位置,并且。。。我错了
QObject.thread
将为每个对象返回不同的QThread实例,但QThread
实际上不是线程,它只是操作系统提供的线程的包装器
因此,代码实际上没有在不同线程中分割多个对象的问题
为了简单起见,我对您用于演示的代码进行了一些修改:
是的,这张照片是:
窗口线程:
TestWidget线程:
TestWidget线程:
演示每个控件在其自己的线程中存在
现在,您有了信号槽机制来处理此“线程安全”,任何其他方法都不会是线程安全的。回答您的问题:
QApplication.exec\(
)。默认情况下,信号和插槽是线程安全的,因为
Qt 4作为某些注释的后续,下面是一个测试脚本,它显示了如何检查代码在哪个线程中执行:
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_())
谢谢你的回答。但是我不理解示例代码,这一定是因为我在信号插槽系统或线程中缺少了一些东西。在代码中,外部对象通过调用其
get\u options
函数,从options
检索值。这样,如果我的外部对象位于另一个线程中,它将执行一个据说有问题的跨线程函数调用。现在,在您的代码中,设置值后会发出信号,只需通知某个小部件
选项发生了问题。是否?@grgrsr这只是一个示例,当您发出信号时,您可以只发出发生变化的选项值,而不是整个选项对象,这样您就不会执行跨线程函数调用。设置该选项后,会发出一个信号,通知所有与其连接的对象某些选项已更改。@RaydelMiranda。您的答案基本上是正确的,但您声称“每个控件都存在于自己的线程中”是完全错误的。您的示例代码所显示的只是每次调用self.thread()
时都会创建一个新的QThread
实例。请记住,QThread
实际上不是线程!它只是一个包装器,用来管理一个最终由操作系统提供的线程。@Ekhumaro,你说得对,我对API做了更多的研究
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>
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_())