PyQt 崩溃和线程安全

grg*_*rsr 5 python qt multithreading pyqt4

你好 StackExchange 社区,

\n

首先,你们给了我很大的帮助,非常感谢。第一次提问:

\n

我目前正在编写一个 PyQt GUI 应用程序,我发现它在 Windows 系统上崩溃了,而且它在我家里的机器上运行时也给我带来了段错误(都是 linux mint 17)。经过一些研究,我意识到我可能创建了一个线程不安全的 GUI,因为我有几个互相调用其他方法的对象。

\n
\n

来自另一个 stackoverflow 问题GUI 小部件只能从主线程访问,即调用 QApplication.exec() 的线程。从任何其他线程 \xe2\x80\x93 访问 GUI 小部件,您对 self.parent() \xe2\x80\x93 的调用正在执行的操作是未定义的行为,在您的情况下,这意味着崩溃。

\n

来自 Qt 文档虽然 QObject 是可重入的,但 GUI 类(尤其是 QWidget 及其所有子类)是不可重入的。它们只能在主线程中使用。如前所述,还必须从该线程调用 QCoreApplication::exec()。

\n
\n

所以最后,我认为我应该只使用信号槽系统来做到这一点。

\n
    \n
  1. 它是否正确?
  2. \n
  3. 这是否仅在函数调用时需要,或者我可以在运行时以线程安全的方式操作其他对象中某些对象的字段吗?例如,我有一个可从多个其他对象访问的选项对象,并且我经常从不同来源更改其中的参数。线程安全还是不安全?
  4. \n
\n

接下来的事情是,我在示例代码中重新创建这种线程不安全行为时遇到问题。Qt 文档说 QObject 存在于不同的线程中。这意味着,以下 Qt 应用程序应该是线程不安全的(如果我正确理解的话)。

\n
from PyQt4 import QtGui\nimport sys\n\nclass TestWidget(QtGui.QWidget):\n    def __init__(self,string):\n        super(TestWidget,self).__init__()\n        self.button = QtGui.QPushButton(string,parent=self)\n        self.button.clicked.connect(self.buttonClicked)\n        \n        # just to check, and yes, lives in it\'s own thread\n        print self.thread()\n        \n    def buttonClicked(self):\n        # the seemingly problematic line\n        self.parent().parent().statusBar().showMessage(self.button.text())\n        pass\n    pass\n\nclass MainWindow(QtGui.QMainWindow):\n    def __init__(self):\n        super(MainWindow,self).__init__()\n        \n        Layout = QtGui.QHBoxLayout()\n        for string in [\'foo\',\'bar\']:\n            Layout.addWidget(TestWidget(string))\n        \n        CentralWidget = QtGui.QWidget(self)\n        CentralWidget.setLayout(Layout)\n        self.setCentralWidget(CentralWidget)\n        self.statusBar()\n        self.show()\n        pass\n    pass\n\nif __name__ == \'__main__\':\n    app = QtGui.QApplication(sys.argv)\n    M = MainWindow()\n    sys.exit(app.exec_())\n
Run Code Online (Sandbox Code Playgroud)\n

但它在我的机器上也可以在 Windows 机器上正常运行。

\n
    \n
  1. 为什么?这实际上是线程不安全的并且可能会崩溃,但事实并非如此?
  2. \n
\n

谢谢你帮我解决这个问题...

\n

Ray*_*nda 3

它是否正确?

是的,您应该只使用信号槽系统来进行 q 对象之间的交互。这就是它的本来面目。

这是否仅在函数调用时需要,或者我可以在运行时以线程安全的方式操作其他对象中某些对象的字段吗?

我有一个可从多个其他对象访问的选项对象...

如果这里的对象指的是 Q 对象:

您的options对象应该支持信号槽机制,您可以optionsQObject.

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)
Run Code Online (Sandbox Code Playgroud)

然后使用此选项的所有小部件/对象都应该有一个连接到此信号的插槽。

一个简单的例子:

    options = Options()
    some_widget = SomeWidget()
    options.optionUpdated.connect(some_widget.options_updated)    // Is like you implement the observer pattern, right?
Run Code Online (Sandbox Code Playgroud)

为什么?这实际上是线程不安全的并且可能会崩溃,但事实并非如此?

thread-unsafe并不意味着“肯定会崩溃”,而是“这可能会崩溃”或“很可能会崩溃”。

来自 pyqt API 文档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_())
Run Code Online (Sandbox Code Playgroud)

是的,这会打印:

Window thread: <PyQt4.QtCore.QThread object at 0x00000000025C1048>
TestWidget thread: <PyQt4.QtCore.QThread object at 0x00000000025C4168>
TestWidget thread: <PyQt4.QtCore.QThread object at 0x00000000025C41F8>
Run Code Online (Sandbox Code Playgroud)

演示每个控件都位于其自己的线程中。

现在,您有了信号槽机制来“线程安全”地处理这个问题,任何其他方法都不是线程安全的。