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

标签 python qt qml qthread pyside2

我有一个 python 定义的 worker 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 时做错了什么/理解错了什么?

最佳答案

说明:

  • 当前执行的“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)
    }
}

关于python - 如何通过长时间运行的插槽保持 UI 响应,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58199357/

相关文章:

python - 如何在 python 中使用 valgrind?

python - 检查是否从R安装了Python

c++ - 在 Qt 中连接多个信号和槽

c++ - 找不到 Qt 5.12 D3D 编译器模块

python - 如何在 Python 3 中的输入后添加字符串?

python - django admin save_model - 为表单字段分配新值

C++ QT libXL 错误 : "During Startup program exited with code 0xc0000135"

c++ - QDomNode 命名空间检测工作不正确

c++ - Qt 编译 - 在 Windows 上使用 qmake

c++ - 包含 QML 中的对象的 QAbstractListModel 有哪些缺点?