在Qt中快速发射信号会导致段错误

时间:2018-02-12 17:13:13

标签: python multithreading qt pyqt qml

我正在使用PyQt5和QML(Qt 5.7.1)构建一个应用程序来控制许多硬件并遇到一个问题,一个接一个地发出10个以上的信号会导致应用程序出现段错误。我附上一个MWE来证明这个问题。

MWE创建一个后台线程,然后使用信号每毫秒更新主线程中的两个标签。 Windows 7和Linux中的示例段错误是随机的,但通常在一秒钟之内。我在Linux中安装了Qt调试符号,发现它在QV4中的随机位置是segfaulting,尽管每个segfaulted的调用似乎都与内存管理器有关。

我已经在这一点上做了一个死胡同,唯一阻止segfaulting的是在每个信号发出之间放置QThread.msleep()个调用,这随着应用程序的增长而变得难以维持。

这是我第一次使用Qt / QML,所以如果这不是使用信号的正确方法我道歉,但我找不到任何说这样使用它们的方法。

StartPage.qml

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0

ApplicationWindow {
    visible: true
    id: mainWindow

    ColumnLayout {
        Label {
            id: runLabel
            property int timesRun: 0

            text: "Number of times run: " + timesRun

            Connections {
                target: worker

                onDoSomethingDone: {
                    runLabel.timesRun = runLabel.timesRun + 1;
                }
            }
        }

        Label {
            id: dataLabel
            property real value: 0.0

            text: "Data: " + value

            Connections {
                target: worker

                onDataChanged: {
                    dataLabel.value = data
                }
            }
        }
    }
}

thread_test.py

#!/usr/bin/env python3

import sys
import os
import signal

from PyQt5.QtCore import QObject, QThread, pyqtSlot, pyqtSignal
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtWidgets import QApplication


class Worker(QObject):

    doSomethingDone = pyqtSignal()
    dataChanged = pyqtSignal(float, arguments=['data'])
    runWorkSignal = pyqtSignal()

    _count = 0.0

    def __init__(self):
        super().__init__()

        self.runWorkSignal.connect(self.do_something)

    @pyqtSlot()
    def do_something(self):
        while (True):
            self._count += 0.5

            self.doSomethingDone.emit()
            self.dataChanged.emit(self._count)

            QThread.msleep(1)


if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    # Switch to the script directory so relative paths work correctly.
    os.chdir(os.path.dirname(os.path.abspath(__file__)))

    app = QApplication(sys.argv)
    engine = QQmlApplicationEngine()

    workObject = Worker()
    workThread = QThread()

    engine.rootContext().setContextProperty('worker', workObject)
    engine.load('qml/StartPage.qml')

    workThread.started.connect(workObject.do_something)
    workObject.moveToThread(workThread)
    workThread.start()

    sys.exit(app.exec_())

1 个答案:

答案 0 :(得分:1)

QML无法直接访问另一个线程,因此如果将项目连接到另一个线程中的对象,则会导致问题,例如您观察到的问题。如果要访问另一个线程,必须从Python端执行,即:

Worker(another thread) --> Proxy(main Thread) --> QML

在您的情况下,解决方案是:

class Worker(QObject):
    [...]

class Proxy(QObject):
    doSomethingDone = pyqtSignal()
    dataChanged = pyqtSignal(float, arguments=['data'])


if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    # Switch to the script directory so relative paths work correctly.
    os.chdir(os.path.dirname(os.path.abspath(__file__)))

    app = QApplication(sys.argv)
    engine = QQmlApplicationEngine()

    workObject = Worker()
    workThread = QThread()
    proxy = Proxy()
    workObject.doSomethingDone.connect(proxy.doSomethingDone)
    workObject.dataChanged.connect(proxy.dataChanged)

    engine.rootContext().setContextProperty('worker', proxy)
    engine.load('qml/StartPage.qml')

    workThread.started.connect(workObject.do_something)
    workObject.moveToThread(workThread)
    workThread.start()

    sys.exit(app.exec_())