【问题标题】:How to maintain UI responsive with long-running slot如何使用长时间运行的插槽保持 UI 响应
【发布时间】:2020-01-31 14:32:09
【问题描述】:

我有一个 python 定义的工作人员 QObject,它有一个慢速 work() 插槽,由 QML UI 调用(在我的实际 UI 中,该方法在 FolderListModel 中的每个项目上作为用户动态调用遍历列表,但对于他的示例代码,我只是在窗口完成时调用它作为示例)。

我想异步运行慢速 work 以防止 UI 阻塞。我想通过在 QThread 上移动 Worker 实例并在那里调用插槽来做到这一点,但这不起作用,因为 UI 仍然被阻止等待 work() 的结果。

这是我目前尝试的代码:

mcve.qml:

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
}

mcve.py:

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_())

如何异步调用work(),以便只有在它完成后才会应用其效果?而且,作为奖励,我在使用 QThreads 时做错了什么/理解错误?

【问题讨论】:

    标签: python qt qml qthread pyside2


    【解决方案1】:

    解释:

    • “work”方法当前在哪里执行?好吧,如果你添加以下代码并检查你得到了什么:
    # ...
    
    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)
        }
    }
    

    【讨论】:

    • 只是一个完美的答案。
    • 确实如此。非常感谢您提供如此高质量且解释清楚的答案!我确实对您的解决方案提出了一些新的疑问,我可能会问这些疑问,但我将首先实现它并尝试完全理解它:)
    猜你喜欢
    • 2010-09-14
    • 1970-01-01
    • 2018-06-03
    • 1970-01-01
    • 2017-07-31
    • 2019-05-03
    • 2010-09-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多