menu Memories' Blog
search self_improvement
目录
Pyside6 Qthread多线程笔记
DNXRZL
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())

参考文章
PyQt QThread
Pyside6 Qthread

分类: 学习笔记
标签: Python