如何通过长时间运行的插槽保持UI响应

时间:2019-10-02 10:02:25

标签: python qt qml qthread pyside2

我有一个由python定义的工作程序QObject,它具有一个缓慢的work()插槽,该插槽由QML UI调用(在我的实际UI中,该方法在{{1 }}随着用户浏览列表而动态变化,但是对于他的示例代码,我只是在窗口完成时调用它作为示例。

我想异步运行慢速FolderListModel以防止UI阻塞。我想通过在QThread上移动Worker实例并在其中调用插槽来实现此目的,但这不起作用,因为UI仍被阻塞,等待work的结果。

这是我到目前为止的尝试代码:

mcve.qml:

work()

mcve.py:

import QtQuick 2.13
import QtQuick.Window 2.13

Window {
    id: window
    visible: true
    width: 800
    height: 600
    title: qsTr("Main Window")

    Component.onCompleted: console.log(worker.work("I'm done!")) // not the actual usage, see note in the question
}

如何异步调用import sys from PySide2.QtWidgets import QApplication from PySide2.QtQml import QQmlApplicationEngine from PySide2.QtCore import QUrl, QThread, QObject, Slot from time import sleep class Worker(QObject): def __init__(self, parent=None): super().__init__(parent) @Slot(str, result=str) def work(self, path): sleep(5) # do something lengthy return path if __name__ == '__main__': app = QApplication(sys.argv) engine = QQmlApplicationEngine() workerThread = QThread() worker = Worker() worker.moveToThread(workerThread) engine.rootContext().setContextProperty("worker", worker) engine.load(QUrl.fromLocalFile('mcve.qml')) if not engine.rootObjects(): sys.exit(-1) sys.exit(app.exec_()) ,以便仅在完成后才应用效果?而且,作为奖励,我在使用QThreads时在做什么/理解错了吗?

1 个答案:

答案 0 :(得分:3)

说明:

  • 当前在哪里执行“工作”方法?好吧,如果您添加以下代码并检查您得到了什么:
# ...

import threading

class Worker(QObject):
    @Slot(str, result=str)
    def work(self, path):
        print(threading.current_thread())
        sleep(5)  # do something lengthy
        return path

# ...

输出:

<_MainThread(MainThread, started 140409078408832)>
qml: I'm done!

如您所见,“ work”方法在主线程中执行,导致其阻塞了GUI。

  • 为什么在主线程中执行“工作”方法?在您所使用的QML中,在主线程中执行的方法或函数是在调用它的上下文中执行的线程。

  • 那么您如何在QObject所在的线程中执行方法?那么您必须使用QMetaObject::invokeMethod()异步执行该方法(对于PySide2,该方法无法用于错误),通过调用信号或使用QTimer::singleShot()


解决方案:

在这些情况下,最好创建一个桥(QObject)来调用在另一个线程中执行的函数/方法,并通过信号通知更改。

import sys
from time import sleep
from functools import partial

from PySide2 import QtCore, QtWidgets, QtQml


class Worker(QtCore.QObject):
    resultChaged = QtCore.Signal(str)

    @QtCore.Slot(str)
    def work(self, path):
        sleep(5)  # do something lengthy
        self.resultChaged.emit(path)


class Bridge(QtCore.QObject):
    startSignal = QtCore.Signal(str)
    resultChaged = QtCore.Signal(str, arguments=["result"])

    def __init__(self, obj, parent=None):
        super().__init__(parent)
        self.m_obj = obj
        self.m_obj.resultChaged.connect(self.resultChaged)
        self.startSignal.connect(self.m_obj.work)


if __name__ == "__main__":

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

    workerThread = QtCore.QThread()
    workerThread.start()

    worker = Worker()
    worker.moveToThread(workerThread)

    bridge = Bridge(worker)

    engine.rootContext().setContextProperty("bridge", bridge)
    engine.load(QtCore.QUrl.fromLocalFile("mcve.qml"))
    if not engine.rootObjects():
        sys.exit(-1)

    ret = app.exec_()
    workerThread.quit()
    workerThread.wait()
    sys.exit(ret)
import QtQuick 2.13
import QtQuick.Window 2.13

Window {
    id: window
    visible: true
    width: 800
    height: 600
    title: qsTr("Main Window")

    Component.onCompleted: bridge.startSignal("I'm done!")

    Connections{
        target: bridge
        onResultChaged: console.log(result)
    }
}