在Qt中,开启多线程的方法有多种,总体分成QThread、QObject、QRunnable、QtConcurrent三大类方法。

而放到PyQt和PySide具体的使用中,使用方法可以说十分类似。

一、继承QThread类及run方法。

此方法可以说是Qt线程的基础,主要方法是重写一个类, 继承QThread类,然后重写run方法。

例如:

# !/usr/bin python3# -*- encoding=utf-8 -*-# Description : # Author: # @Email : # @File : 01_thread.py# @Time : 2023-06-27 15:37:08# @Project : qtfrom PySide6.QtCore import QThread, Signalfrom PySide6.QtWidgets import QWidget, QApplication, QPushButtonclass Work(QThread):result = Signal()def __init__(self):QThread.__init__(self)def run(self) -> None:for i in range(0, 10):print('i = {}'.format(i))self.usleep(1000000)self.result.emit()class Widget(QWidget):def __init__(self):QWidget.__init__(self)self._work = Work()self._work.result.connect(self._result)self._widget()def _widget(self):_btn = QPushButton('确定')_btn.setParent(self)_btn.clicked.connect(self._run)def _run(self):self._work.start()def _result(self):print('线程结束')if __name__ == '__main__':app = QApplication()window = Widget()window.show()app.exec()

此方法的特点:

1、优点:可以通过信号槽与外界进行通信。2、缺点:1)每次新建一个线程都需要继承QThread,实现一个新类,使用不太方便。2)要自己进行资源管理,线程释放和删除。并且频繁的创建和释放会带来比较大的内存开销。3、适用场景:QThread适用于那些常驻内存的任务。

二、使用QObject类的moveToThread方法,调用QThread执行线程

创建一个继承QObject的类(如Work类),然后new一个Qthread,并把创建的Work类moveToThread到创建好的子线程中,然后start方法启动子线程,这样就实现了一个子线程。主线程通过发送信号,调用Work中的方法,从而实现在子线程中的计算。

# !/usr/bin python3# -*- encoding=utf-8 -*-# Description : # Author: # @Email : # @File : 02_object.py# @Time : 2023-06-27 16:00:40# @Project : qtfrom PySide6.QtCore import QThread, Signal, QObjectfrom PySide6.QtWidgets import QWidget, QApplication, QPushButtonclass Work(QObject):result = Signal()is_run = Falsedef __init__(self):QObject.__init__(self)def run(self) -> None:for i in range(0, 10):print('i = {}'.format(i))QThread.usleep(1000000)self.result.emit()def start(self):self.is_run = Truedef stop(self):self.is_run = Falseclass Widget(QWidget):def __init__(self):QWidget.__init__(self)self._widget()def _widget(self):_btn = QPushButton('确定')_btn.setParent(self)_btn.clicked.connect(self._run)def _run(self):_work = Work()_thread = QThread()_work.result.connect(self._result)_work.moveToThread(_thread)def _result(self):print('线程结束')if __name__ == '__main__':app = QApplication()window = Widget()window.show()app.exec()

此方法的特点:

moveToThread对比传统子类化Qthread更灵活,仅需要把你想要执行的代码放到槽,moveToThread这个object到线程,然后拿一个信号连接到这个槽就可以让这个槽函数在线程里执行。可以说,moveToThread给我们编写代码提供了新的思路,当然不是说子类化qthread不好,只是你应该知道还有这种方式去调用线程。

轻量级的函数可以用moveToThread,多个短小精悍能返回快速的线程函数适用 ,无需创建独立线程类,例如你有20个小函数要在线程内做, 全部扔给一个QThread。而我觉得moveToThread和子类化QThread的区别不大,更可能是使用习惯引导。又或者你一开始没使用线程,但是后边发觉这些代码还是放线程比较好,如果用子类化QThread的方法重新设计代码,将会有可能让你把这一段推到重来,这个时候,moveToThread的好处就来了,你可以把这段代码的从属着moveToThread,把代码移到槽函数,用信号触发它就行了。其它的话moveToThread它的效果和子类化QThread的效果是一样的,槽就相当于你的run()函数,你往run()里塞什么代码,就可以往槽里塞什么代码,子类化QThread的线程只可以有一个入口就是run(),而moveToThread就有很多触发的入口。

三、使用QRunnable类

Qrunnable是所有可执行对象的基类。我们可以继承Qrunnable,并重写虚函数void QRunnable。run () 。

我们可以用QThreadPool让我们的一个QRunnable对象在另外的线程中运行,如果autoDelete()返回true(默认),那么QThreadPool将会在run()运行结束后自动删除Qrunnable对象。可以调用void QRunnable::setAutoDelete ( bool autoDelete )更改auto-deletion标记。

需要注意的是,必须在调用QThreadPool::start()之前设置,在调用QThreadPool::start()之后设置的结果是未定义的。

一、实现方法:

1、继承QRunnable。和QThread使用一样, 首先需要将你的线程类继承于QRunnable。

2、重写run函数。还是和QThread一样,需要重写run函数,run是一个纯虚函数,必须重写。

3、使用QThreadPool启动线程

代码示例:

# !/usr/bin python3# -*- encoding=utf-8 -*-# Description : # Author: # @Email : # @File : 03_runable.py# @Time : 2023-06-27 16:07:54# @Project : qtfrom PySide6.QtCore import QThread, Signal, QObject, QRunnable, QThreadPoolfrom PySide6.QtWidgets import QWidget, QApplication, QPushButtonclass Runnable(QRunnable):def __init__(self):QRunnable.__init__(self)def run(self):for i in range(0, 10):print('i = {}'.format(i))QThread.usleep(1000000)class Widget(QWidget):def __init__(self):QWidget.__init__(self)self._widget()def _widget(self):_btn = QPushButton('确定')_btn.setParent(self)_btn.clicked.connect(self._run)def _run(self):_run_able = Runnable()_run_able.setAutoDelete(True)# Auto-deletion is enabled by default_pool = QThreadPool.globalInstance()_pool.start(_run_able)# _pool.waitForDone()# 会阻塞UI# 可同时启动多个线程# QRunnable 不是QObject的子类 不能使用Signal, 不能使用信号槽与外界通信。if __name__ == '__main__':app = QApplication()window = Widget()window.show()app.exec()

二、此方法的特点:

优点:无需手动释放资源,QThreadPool启动线程执行完成后会自动释放。缺点:不能使用信号槽与外界通信。适用场景:QRunnable适用于线程任务量比较大,需要频繁创建线程。QRunnable能有效减少内存开销。

四、使用QRunnable类中的静态方法create创建新线程

原理还是使用了QRunnable类来调用和开启新线程,但因为QRunnable类提供了一个静态方法->create方法, 所以可以实现在不写新类的情况下,直接开启和运行新线程。

代码示例:

# !/usr/bin python3# -*- encoding=utf-8 -*-# Description : # Author: # @Email : # @File : 04_runable.py# @Time : 2023-06-27 16:24:47# @Project : qtfrom PySide6.QtCore import QThread, QRunnable, QThreadPoolfrom PySide6.QtWidgets import QWidget, QApplication, QPushButtonclass Widget(QWidget):def __init__(self):QWidget.__init__(self)self._widget()def _widget(self):_btn = QPushButton('确定')_btn.setParent(self)_btn.clicked.connect(self._run)def _run(self):_run_able = QRunnable.create(self._runnable)_run_able.setAutoDelete(True)# Auto-deletion is enabled by default_pool = QThreadPool.globalInstance()_pool.start(_run_able)# _pool.waitForDone()# 会阻塞UIdef _runnable(self) -> None:for i in range(0, 10):print('i = {}'.format(i))QThread.usleep(500000)if __name__ == '__main__':app = QApplication()window = Widget()window.show()app.exec()

五、QtConcurrent,并不能实现。

在Qt中,还有一种可以开启新线程的方法,QtConcurrent。

但遗憾的是,不管是在PyQt中,还是在PySide中,都没有方法可以调用此方式。

因为这个实现方法调用的是一个namespace, 而不是一个类,因此无法封装。