Issue
I am building an application using PyQt5 and QML (Qt 5.7.1) that controls a number of pieces of hardware and have run into a problem where emitting 10+ signals one after another causes the application to segfault. I attached a MWE to demonstrate the problem.
The MWE creates a background thread which then updates two labels in the main thread every millisecond using signals. The example segfaults in both Windows 7 and linux randomly, but generally in under a second. I installed the Qt debugging symbols in linux and found that it was segfaulting in random locations in QV4, though each call that segfaulted appeared to have something to do with the memory manager.
I've hit a dead end on what to do at this point, the only thing that stopped the segfaulting was to place QThread.msleep()
calls between each signal emit, which has become untenable as the application grows.
This is my first time using Qt/QML so if this is not the correct way to use signals I apologize, but I can't find anything that says don't use them this way.
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_())
Solution
QML can not access another thread directly so if you connect an item to an object that is in another thread it causes problems like the one you observe. If you want to access another thread you must do it from the Python side, that is:
Worker(another thread) --> Proxy(main Thread) --> QML
In your case the solution is:
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_())
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.