我试图了解如何使用 QThreads 运行后台服务,在后台处理数据而不冻结 UI。虽然我弄清楚了如何在 python 中执行此操作,但 QML 的工作方式似乎有所不同。
我设置了一个显示问题的小示例应用程序(代码后继续解释):
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 多线程信号允许我通过将方法连接到 GUI 线程发出的信号来在不同的线程中运行该方法。这是有效的,正如
doInitOnTheThread
在第二个线程上执行的事实所示。但是,如果我在 QML 中重复相同的模式,该方法就会在 GUI 线程中运行。
如何让它在后台线程中运行?一个(可怕的,IMO)可能的解决方案是在 python 中创建另一个类,充当 QML 和 python 之间的代理,其实例将位于 GUI 线程的上下文中。然后,我将 QML 信号注册到代理中的插槽,并在代理插槽的实现中,我从 python 发出一个信号,然后将其正确传递到另一个线程。
有更合理的解决方案吗?或者 QML 真的无法发出信号将对象移动到其他线程吗?
在您的代码中,mainThread(18408)创建qml上下文。这意味着qml组件也运行在mainThread上。当您调用将(setContextProperty)注入QML的python类实例时,您调用的地方,它运行的地方。
除非您使用参数 type=Qt.ConnectionType.QueuedConnection。这样就需要在main.py中连接signal&slot。所以我们找到另一种方法,如果python类有一个名为“testSlot”的方法(Slot),你可以创建一个任何名称的信号,例如“testSignal”,这个信号添加到python类中。在 main.py 中使用:
controller.testSignal.connect(controller.testSlot,
type=Qt.ConnectionType.QueuedConnection)
在 qml 中,使用:
controller.testSignal()
相当于调用TestSlot。但现在,是类实例调用自己,想想我刚才说的那句话:你调用到哪里,它就运行到哪里
但是这种方式也有bug,QueuedConnection意味着它不等待函数返回, 所以如果一个槽有返回值,在 qml 中你可能会收到 undefind。
虽然这个问题已经持续了很长时间,但我想为那些和我一样困扰的人提供一些经验