Reputation: 19153
I'm trying to understand how to use QThreads to run a background service that processes data in the background without freezing the UI. While I figured out how to do this in python, it seems that QML works differently.
I set up a small example application that shows the problem (explanation continues after code):
import threading
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQuick import QQuickView
import os
class MyBackgroundProcessingClass(QObject):
def __init__(self, **kwargs):
return super().__init__(**kwargs)
@pyqtSlot()
def doInitOnTheThread(self):
# allocate lots of resources
print('doInitOnTheThread() executing in thread {}'.format(threading.get_ident()))
@pyqtSlot(int)
def myProcessingFunctionThatTakesALotOfTime(self, the_parameter):
# process for a long time
print('myProcessingFunctionThatTakesALotOfTime() executing in thread {}'.format(threading.get_ident()))
if __name__ == '__main__':
print('GUI thread has ID {}'.format(threading.get_ident()))
myApp = QApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
the_thread = QThread()
my_background_processing_instance = MyBackgroundProcessingClass()
my_background_processing_instance.moveToThread(the_thread)
the_thread.start()
QTimer.singleShot(0, my_background_processing_instance.doInitOnTheThread) #sending a signal to the thread to do the init in its own context
view.engine().rootContext().setContextProperty("myExternalInstance", my_background_processing_instance)
view.setSource(QUrl('example.qml'))
view.show()
myApp.exec_()
os.system("pause")
import QtQuick 2.0
import QtQuick.Controls 1.2
Button {
signal myCustomSignalWithParameters(int param)
text: "Click me!"
onClicked: {
myCustomSignalWithParameters(42)
}
Component.onCompleted: {
myCustomSignalWithParameters.connect(myExternalInstance.myProcessingFunctionThatTakesALotOfTime)
}
}
GUI thread has ID 18408
doInitOnTheThread() executing in thread 11000
myProcessingFunctionThatTakesALotOfTime() executing in thread 18408
PyQt multithreaded signaling allows me to run a method in a different thread by connecting the method to a signal emitted by the GUI thread. This is working, as shown by the fact that doInitOnTheThread
executes on a second thread. If, however, I repeat the same pattern in QML, the method runs in the GUI thread instead.
How do I make it run in the background thread? A (horrible, IMO) possible solution would be to make yet another class in python that acts as proxy between QML and python, whose instance would be on the GUI thread's context. I'd then register the QML signals with slots in the proxy and in the proxy slots' implementations I'd emit a signal from python that would then be passed correctly to the other thread.
Is there a more reasonable solution? Or is QML really incapable of signalling objects moved to other threads?
Upvotes: 1
Views: 1176
Reputation: 11
In your code, mainThread(18408) create qml context.That means qml component also run on the mainThread.When you call a python class instance which inject(setContextProperty) into QML, where you call, where it run.
Unless you use the param type=Qt.ConnectionType.QueuedConnection. In that way, you need connect signal&slot in main.py. So we find another way, if python class has a method(Slot) names "testSlot", you can create a signal with any name,such as "testSignal", this signal add into python class. In the main.py use:
controller.testSignal.connect(controller.testSlot,
type=Qt.ConnectionType.QueuedConnection)
In the qml, use:
controller.testSignal()
That equivalent to call TestSlot. But now, is the class instance call itself, Think about the sentence I just said: where you call, where it run
But this way also has bug, QueuedConnection means it not wait function return, so if a slot have return value, in the qml you may receive undefind.
Although this issue has been going on for a long time, I want to provide some experience for people who are as troubled as I used to be
Upvotes: 0