PyQt崩溃和线程安全

时间:2015-04-14 12:10:49

标签: python multithreading qt pyqt4

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()。

所以最后,我认为我应该只使用信号槽系统。

  1. 这是对的吗?
  2. 这只是函数调用所需要的,还是我可以在线程安全的方式在运行时从其他对象操作某些对象的字段?例如,我有一个从多个其他对象访问的选项对象,我经常从不同的源更改参数。线程安全还是不安全?
  3. 接下来,我在示例代码中重新创建此线程不安全行为时遇到问题。 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_())
    

    但它在我的我的手机上运行也很好。

    1. 为什么?这实际上是线程不安全而且可能崩溃,但它只是没有?
    2. 感谢您帮助我解决这个问题......

3 个答案:

答案 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)

您的问题的答案:

  1. GUI小部件只能从主线程(运行的线程)访问 QApplication.exec_())。默认情况下,信号和插槽是线程安全的 Qt 4

  2. 任何导致直接 Qt图形对象操作的调用来自另一个线程而不是主线程线程安全=&gt;会崩溃

  3. 你的问题代码中没有涉及线程(线程在哪里?), 不同的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_())