Pyside6 + QML - 多线程01 - QThread 基础(子线程执行耗时任务)
导言
在现代图形化应用程序中,用户界面的流畅性至关重要。尤其是在需要执行耗时任务时,如何保证UI的响应性成为开发者的一大挑战。PySide6 提供了强大的多线程支持,使得开发者能够将耗时的计算任务放入后台线程中执行,从而避免阻塞主线程的 UI 响应。
在这一章节中,将介绍如何使用 QThread 来在子线程中执行耗时任务,同时利用信号机制将任务结果安全地传递回UI线程。通过这种方式,应用程序能够在执行后台任务的同时保持界面的高响应性,提升用户体验。我们将通过具体示例,展示如何使用 Worker + QThread 模式,轻松实现这一功能。
效果如下:
工程代码:
- github: https://github.com/q164129345/myPyside6_QML/tree/main/thread01_begin
- gitee: https://gitee.com/wallace89/myPyside6_QML/tree/main/thread01_begin
一、main.py
# python3.10.11 - PySide6==6.9
import sys, time
from PySide6.QtCore import QObject, Signal, Slot, QThread
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngineclass Worker(QObject):finished = Signal(str) # 定义线程结束信号,传递字符串参数def run(self):for i in range(5):print(f"[Python] 线程运行中... {i+1}/5")time.sleep(1) # 模拟耗时操作self.finished.emit("任务完成!来自子线程") # 任务完成,发射信号class Backend(QObject):resultReady = Signal(str) # 定义信号,传递字符串参数def __init__(self):super().__init__() # 初始化父类self.thread = Noneself.worker = None@Slot()def startTask(self):# 创建子线程和Worker对象self.thread = QThread()self.worker = Worker()self.worker.moveToThread(self.thread) # 将Worker对象移到子线程# 线程启动->调用worker的run方法self.thread.started.connect(self.worker.run) # 线程启动时调用Worker的run方法# worker完成-> 发信号给UIself.worker.finished.connect(self.resultReady.emit) # 将结果通过信号传递# worker完成->退出线程self.worker.finished.connect(self.thread.quit)self.worker.finished.connect(self.worker.deleteLater) # 任务完成后删除worker对象self.thread.finished.connect(self.thread.deleteLater) # 线程结束后删除线程对象self.thread.start() # 启动线程print("[Backend] 子线程已启动...")if __name__ == "__main__":# 创建应用程序和引擎app = QGuiApplication(sys.argv)engine = QQmlApplicationEngine()# qml与python交互backend = Backend() # 实例化python后端对象engine.rootContext().setContextProperty("backend", backend) # 注册到QML环境(名叫 “backend”)# 加载QML文件engine.addImportPath(sys.path[0]) # 当前项目路径engine.loadFromModule("Example", "Main") # 模块(Example) + QML文件名(Main.qml)if not engine.rootObjects():sys.exit(-1)sys.exit(app.exec())
关键点说明
@Slot()
def startTask(self):# 创建子线程和Worker对象self.thread = QThread()self.worker = Worker()self.worker.moveToThread(self.thread) # 将Worker对象移到子线程# 线程启动->调用worker的run方法self.thread.started.connect(self.worker.run) # 线程启动时调用Worker的run方法# worker完成-> 发信号给UIself.worker.finished.connect(self.resultReady.emit) # 将结果通过信号传递# worker完成->退出线程self.worker.finished.connect(self.thread.quit)self.worker.finished.connect(self.worker.deleteLater) # 任务完成后删除worker对象self.thread.finished.connect(self.thread.deleteLater) # 线程结束后删除线程对象self.thread.start() # 启动线程print("[Backend] 子线程已启动...")
这几行是 QThread + Worker 模式的标准写法。
它们的作用是:建立信号和槽的连接,保证子线程能正确启动、退出、清理。
-
self.thread = QThread()
:新建一个线程对象(还没真正跑起来)。给耗时任务准备一个独立执行环境,避免卡住 UI。 -
self.worker = Worker()
:创建执行耗时任务的对象。把耗时逻辑集中到Worker.run()
,便于复用与测试。注意:Worker
必须无父对象,才允许迁移到其它线程。 -
self.worker.moveToThread(self.thread)
:把Worker
所属线程切换到self.thread
。确保之后通过信号/事件驱动调用到的Worker
槽函数在子线程执行。只改变“线程亲和性”,不会自动调用run()
。 -
self.thread.started.connect(self.worker.run)
:当线程事件循环真正启动时,自动调用worker.run()
。保证run()
在子线程里执行(这行配合上面的moveToThread
实现)。这是跨线程连接,Qt 会自动使用 Queued Connection。 -
self.worker.finished.connect(self.resultReady.emit)
:把Worker
的finished(str)
直接转发给Backend.resultReady(str)
。把结果抛回主线程给 QML;这是常用的“信号转信号”写法。finished
在子线程发出,但resultReady
会在主线程排队处理,UI 安全。 -
self.worker.finished.connect(self.thread.quit)
:任务一完成,通知线程退出事件循环。不用的线程应尽快停掉,避免一直空转占资源。 -
self.worker.finished.connect(self.worker.deleteLater)
:标记Worker
为“稍后删除”(由 Qt 事件循环安全回收)。避免立即del
引发跨线程销毁风险;deleteLater
会在其所属线程安全清理。很多示例也会用self.thread.finished.connect(self.worker.deleteLater)
,两者都常见;
你的写法在quit()
前发出删除事件,也会被安全处理。 -
self.thread.finished.connect(self.thread.deleteLater)
:线程真正结束后,安全销毁QThread
对象本身。彻底释放资源,避免“僵尸线程”或内存泄漏。 -
self.thread.start()
: 启动子线程事件循环 → 触发上面的started
信号 →worker.run()
开始干活。一定要先连接好所有信号再start()
,否则可能错过started
。
二、Main.qml
// 导入QtQuick模块,提供基本的QML元素
import QtQuick
// 导入QtQuick.Controls模块,提供UI控件如Button
import QtQuick.Controls// 定义一个窗口组件,作为应用程序的主窗口
Window {// 设置窗口宽度width: 320// 设置窗口高度height: 240// 设置窗口可见visible: true// 设置窗口标题title: "多线程01 - 基础示例"// 使用Column布局,垂直排列子元素Column {// 将Column居中对齐到父元素(窗口)anchors.centerIn: parent// 设置子元素之间的间距spacing: 20// 定义一个按钮Button {// 设置按钮文本text: "开始耗时任务"// 定义按钮点击事件处理函数onClicked: {// 调用backend对象的startTask方法,开始任务backend.startTask()// 更新结果文本为“任务进行中...”resultText.text = "任务进行中..."}}// 定义一个文本元素,用于显示结果Text {// 设置文本元素的ID,用于在代码中引用id: resultText// 设置初始文本text: "等待结果..."// 设置字体大小font.pointSize: 18}}// 使用Connections对象来连接信号和槽Connections {// 设置目标对象为backendtarget: backend// 定义信号处理函数,当backend发出resultReady信号时调用function onResultReady(msg) {// 更新结果文本为信号传递的消息resultText.text = msg}}
}
qml代码看注释吧,很简单。