QML:向位于不同 QThread 中的对象发送信号

问题描述 投票:0回答:1

我试图了解如何使用 QThreads 运行后台服务,在后台处理数据而不冻结 UI。虽然我弄清楚了如何在 python 中执行此操作,但 QML 的工作方式似乎有所不同。

我设置了一个显示问题的小示例应用程序(代码后继续解释):

示例.py

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")

示例.qml

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 真的无法发出信号将对象移动到其他线程吗?

python qt pyqt qml pyqt5
1个回答
0
投票

在您的代码中,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。

虽然这个问题已经持续了很长时间,但我想为那些和我一样困扰的人提供一些经验

© www.soinside.com 2019 - 2024. All rights reserved.