Pyside6 Qthread多线程笔记

DNXRZL
2023年02月03日 · 阅读 659
故事
最近上手了Pyside6,不得不说Qt就是好啊,入门很轻松,只有几个难点,例如Qthread
吐槽
Pyside由于历史原因,很多文章讲的都是自己定义一个类然后去继承Qthread,重写run()方法来实现多线程,这种方法也可以,但官方不赞成,想必这里面肯定有不赞成的道理,我也不懂,也没法去深究,既然官方不赞成那就用官方的写法吧!
官方推荐写法
首先创建一个类,并继承QObject
这个类负责执行耗时的工作,所以称它为工作类,类里需用到信号和槽便继承了QObject
class Worker(QObject):
def do_some_work(self):
pass
然后创建一个线程和一个工作实例
self.worker = Worker() # 工作实例
self.worker_thread = QThread() # 创建的线程实例
再把工作实例放到刚创建的线程里
# 由于worker类继承了QObject,所以他有moveToThread()方法
self.worker.moveToThread(self.worker_thread)
然后启动线程
注意:线程启动了,线程内工作实例的工作不会自动执行,那如何执行?去看下面
self.worker_thread.start()
线程内的工作实例开始执行工作
注意:线程内工作实例的工作(即方法do_some_work())都应该用信号与槽来调用,而不是从主线程直接调用,例如:self.worker.do_some_work() # 不要这样
# 正确做法(使用信号与槽)
# 先定义一个全局信号 a = Signal(str)
# 然后将信号a与工作实例的方法(do_some_work())绑定
# 新建一个函数,给a发信号即可开始执行(do_some_work())任务
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QPushButton, QVBoxLayout, QProgressBar
from PySide6.QtCore import QThread, QObject, Signal
import time
# 工作类,并继承QObject
class Worker(QObject):
progress = Signal(int)
completed = Signal(int)
# 需要执行的耗时任务
def do_work(self, n):
for i in range(1, n+1):
time.sleep(1)
self.progress.emit(i)
self.completed.emit(i)
class MainWindow(QMainWindow):
a = Signal(int) #全局信号
def __init__(self):
super(MainWindow, self).__init__()
# ui部分
self.setGeometry(100, 100, 300, 50)
self.setWindowTitle('QThread Demo')
self.widget = QWidget()
layout = QVBoxLayout()
self.widget.setLayout(layout)
self.setCentralWidget(self.widget)
self.progress_bar = QProgressBar(self)
self.progress_bar.setValue(0)
self.btn_start = QPushButton('Start', clicked=self.start)
layout.addWidget(self.progress_bar)
layout.addWidget(self.btn_start)
# 创建一个线程和一个工作实例
self.worker = Worker()
self.worker_thread = QThread()
###########注意#########################################################################################
# 官方还有一个deleteLater()方法,该方法来自工作实例(即self.worker),因为继承了QObject
# 有什么作用?大白话就是把刚放进线程里的工作实例释放掉(拿出来、删掉),这样,线程里就什么都没有了
# 例子:self.worker_thread.finished.connect(self.worker.deleteLater)
# 线程里没有工作实例了,自然就无法再次调用了,看如下报错
# Traceback (most recent call last):
# File "D:\Py Workspace\baseqt\test.py", line 49, in <lambda>
# self.worker_thread.finished.connect(lambda: print(self.worker))
# RuntimeError: Internal C++ object (Worker) already deleted.
# 报错已经说的很明白了,object (Worker) already deleted
# 什么时候用?多数是不用的,看情况而定吧!
# 那他是为了什么?一种优化,释放掉不用的对象,释放内存呗,注意了,你的对象还要用就不要去释放了
#######################################################################################################
# self.worker_thread.finished.connect(self.worker.deleteLater)
# self.worker_thread.finished.connect(lambda: print(self.worker)) # 测试释放掉对象后的报错
# 线程里的progress信号与进度条更新函数绑定
self.worker.progress.connect(self.update_progress)
# 线程里的completes信号与完成函数绑定
self.worker.completed.connect(self.complete)
# 全局信号绑定工作实例的方法
self.a.connect(self.worker.do_work)
# 把工作实例放进线程里
self.worker.moveToThread(self.worker_thread)
# 开始线程
self.worker_thread.start()
def start(self):
self.btn_start.setEnabled(False)
n = 5
self.progress_bar.setMaximum(n)
self.a.emit(n) # 给全局信号发信号,触发线程内工作实例的方法执行
def update_progress(self, v):
self.progress_bar.setValue(v) # 与线程内的progress信号绑定,更新进度条
def complete(self, v): # 与线程内completed信号绑定,线程工作一结束就会触发此函数
self.progress_bar.setValue(v)
self.btn_start.setEnabled(True)
# 线程内的耗时任务执行完了,但创建的这个线程不一定也会结束,所以还需下面几句来主动退出
print(self.worker_thread.isRunning()) # 打印True表示线程还在
self.worker_thread.quit() # 结束线程
self.worker_thread.wait() # 等待线程结束
print(self.worker_thread.isRunning()) # 打印False表示线程已退出
# 注意:没有quit()和wait(),在x掉窗口时控制台会报“QThread: Destroyed while thread is still running”
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())