目录
三、QObject.moveToThread+QThread
PyQt5不支持Qt Concurrent模块,所以PyQt5多线程的实现方式是以下三种:
QThreadPool+QRunnable(详细介绍)
子类化QThread(详细介绍)
QObject.moveToThread+QThread(详细介绍)
下面通过这三种方式实现一个同样功能的计时器界面程序。
一、QThreadPool+QRunnable
QThreadPool(线程池)管理和回收多个QRunnable对象,以帮助降低线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用QThreadPool.globalInstance()来访问该象。
要使用QThreadPool执行线程,请将QRunnable子类化并实现run()函数,然后创建该类的对象,并将其传递给QThreadPool的start()。
默认情况下,QThreadPool会自动删除执行完的QRunnable。可以使用QRunnable的setAutoDelete()更改自动删除标志。
在一定时间内未使用的线程将过期。默认的过期超时为30000毫秒(30秒)。这可以使用setExpiryTimeout()进行更改。设置负到期超时将禁用到期机制。
调用maxThreadCount()查询要使用的最大线程数。如果需要,可以使用setMaxThreadCount()更改限制。默认的最大线程计数是QThread.idealThreadCount()。activeThreadCount()函数返回当前正在执行工作的线程数。
QRunnable没有继承QObject,所以在QRunnable的子类中是无法定义信号的。
import time
import sys
from PyQt5.QtCore import QThreadPool, QRunnable, QObject, pyqtSignal
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel, QApplication
class Signals(QObject):
# 将信号统一定义在一个QObject子类中
signal_timer_end = pyqtSignal()
signal_timer_cur_time = pyqtSignal(int)
# 实例化Signals以便使用信号,千万不能直接以Signals.signal_timer_end 的方式使用信号
SignalInstance = Signals()
class TimerThread(QRunnable):
# 继承QRunnable实现一个线程类TimerThread
# TimerThread里是不能定义信号的
def __init__(self):
super().__init__()
# 外部无法控制QThreadPool里的线程,所以只能通过定义标志位的方式
self.stop = False
self.cur_time = 0
SignalInstance.signal_timer_end.connect(self.stop_timer)
def stop_timer(self):
# 控制线程结束
self.stop = True
def run(self) -> None:
# 一旦结束while循环,退出run,QThreadPool就会自动销毁TimerThread
while not self.stop:
SignalInstance.signal_timer_cur_time.emit(self.cur_time)
time.sleep(0.5)
self.cur_time += 1
class DemoWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("测试窗口1")
self.resize(700, 500)
self.setCentralWidget(QWidget())
self.main_layout = QVBoxLayout()
self.centralWidget().setLayout(self.main_layout)
self.main_layout.addWidget(QLineEdit())
self.header_layout = QHBoxLayout()
self.btn_timer_start = QPushButton("扫描")
self.btn_timer_start.clicked.connect(self.timer_start)
self.header_layout.addWidget(self.btn_timer_start)
self.btn_timer_stop = QPushButton("停止")
self.btn_timer_stop.clicked.connect(self.timer_stop)
self.header_layout.addWidget(self.btn_timer_stop)
self.main_layout.addLayout(self.header_layout)
self.l_timer = QLabel()
self.main_layout.addWidget(self.l_timer)
SignalInstance.signal_timer_cur_time.connect(self.set_cur_time)
def closeEvent(self, a0) -> None:
SignalInstance.signal_timer_end.emit()
def set_cur_time(self, cur_time):
self.l_timer.setText(str(cur_time))
@classmethod
def timer_start(cls):
# 获取全局的QThreadPool对象执行线程
QThreadPool.globalInstance().start(TimerThread())
@classmethod
def timer_stop(cls):
SignalInstance.signal_timer_end.emit()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DemoWindow()
window.show()
sys.exit(app.exec_())
二、子类化QThread
QThread对象管理程序中的一个线程。QThread在run()中开始执行。默认情况下,run()通过调用exec()启动事件循环,并在线程内运行Qt事件循环。QThread通过start()启动。
QThread的实例化相关代码还是在旧线程中执行的,run()是在新线程中执行的。
当线程启动和结束时,QThread会通过信号started和finished通知您,或者您可以使用isFinished()和isRunning()来查询线程的状态。
您可以通过调用exit()或quit()来停止线程。在极端情况下,您可以调用terminate()来强制终止一个正在执行的线程。然而,这样做是危险的,也是令人沮丧的。(但是很多时候exit和quit根本不好使,还是terminate好使)
使用wait()会阻塞调用线程,直到线程完成执行(或直到指定的时间过去)。wait()函数通常是不必要的,因为Qt是一个事件驱动的框架。与其wait(),不如考虑监听finished信号。
QThread还提供与平台无关的睡眠函数:sleep()、msleep()和usleep()分别为秒、毫秒和微秒级。
QThread.currentThreadId()和QThread.currentThread()返回当前执行线程的标识符。前者返回线程的特定于平台的ID;后者返回一个QThread指针。
import time
import sys
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel, QApplication
class TimerThread(QThread):
# 子类化一个QThread
# QThread继承自QObject,是可以定义信号的
signal_timer_cur_time = pyqtSignal(int)
def __init__(self):
super().__init__()
self.cur_time = 0
def run(self) -> None:
self.cur_time = 0
# QThread可以通过terminate强制终止,这里就没用标志位控制退出了
while True:
self.signal_timer_cur_time.emit(self.cur_time)
time.sleep(0.3)
self.cur_time += 1
class DemoWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("测试窗口3")
self.resize(700, 500)
self.setCentralWidget(QWidget())
self.main_layout = QVBoxLayout()
self.centralWidget().setLayout(self.main_layout)
self.main_layout.addWidget(QLineEdit())
self.header_layout = QHBoxLayout()
self.btn_timer_start = QPushButton("扫描")
self.btn_timer_start.clicked.connect(self.timer_start)
self.header_layout.addWidget(self.btn_timer_start)
self.btn_timer_stop = QPushButton("停止")
self.btn_timer_stop.clicked.connect(self.timer_stop)
self.header_layout.addWidget(self.btn_timer_stop)
self.main_layout.addLayout(self.header_layout)
self.l_timer = QLabel()
self.main_layout.addWidget(self.l_timer)
# 实例化一个TimerThread
self.timer_thread = TimerThread()
self.timer_thread.signal_timer_cur_time.connect(self.set_cur_time)
def closeEvent(self, a0) -> None:
self.timer_stop()
def set_cur_time(self, cur_time):
self.l_timer.setText(str(cur_time))
def timer_start(self):
self.timer_thread.start()
def timer_stop(self):
# 官网说terminate很危险,我却觉得很好使啊,当然了,如果真的觉得危险
# 也可以像上面一样通过标志位控制退出
self.timer_thread.terminate()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DemoWindow()
window.show()
sys.exit(app.exec_())
三、QObject.moveToThread+QThread
这种方式是子类化QObject,将业务逻辑放在这个子类中,然后通过moveToThread把QThread和这个子类需要在新线程中执行的方法连接起来,在新线程中执行这个方法,具体看代码:
import time
import sys
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel, QApplication
class Worker(QObject):
# Worker 子类化QObject
signal_timer_cur_time = pyqtSignal(int)
def __init__(self):
super().__init__()
self.cur_time = 0
self.stop = False
def stop_worker(self):
# 通过标志位控制停止worker
self.stop = True
def run(self) -> None:
self.cur_time = 0
if self.stop:
self.stop = False
while not self.stop:
self.signal_timer_cur_time.emit(self.cur_time)
time.sleep(0.1)
self.cur_time += 1
class DemoWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("测试窗口5")
self.resize(700, 500)
self.setCentralWidget(QWidget())
self.main_layout = QVBoxLayout()
self.centralWidget().setLayout(self.main_layout)
self.main_layout.addWidget(QLineEdit())
self.header_layout = QHBoxLayout()
self.btn_timer_start = QPushButton("扫描")
self.btn_timer_start.clicked.connect(self.timer_start)
self.header_layout.addWidget(self.btn_timer_start)
self.btn_timer_stop = QPushButton("停止")
self.btn_timer_stop.clicked.connect(self.timer_stop)
self.header_layout.addWidget(self.btn_timer_stop)
self.main_layout.addLayout(self.header_layout)
self.l_timer = QLabel()
self.main_layout.addWidget(self.l_timer)
# QThread不需要子类化了,直接实例化
self.timer_thread = QThread()
# 实例化Worker
self.worker = Worker()
# 指定worker在timer_thread中执行
self.worker.moveToThread(self.timer_thread)
# 把timer_thread的started信号和worker的run连接起来,这样调用timer_thread.start()
# 就会自动执行worker.run()
self.timer_thread.started.connect(self.worker.run)
self.worker.signal_timer_cur_time.connect(self.set_cur_time)
def closeEvent(self, a0) -> None:
self.worker.stop_worker()
self.timer_thread.terminate()
self.timer_thread.wait()
def set_cur_time(self, cur_time):
self.l_timer.setText(str(cur_time))
def timer_start(self):
# 启动timer_thread,worker.run()也开始执行
self.timer_thread.start()
def timer_stop(self):
# 停止worker
self.worker.stop_worker()
# 这里quit可以生效是因为run已经退出了,如果run里面还是while True的方式,那quit是无法启作用的
self.timer_thread.quit()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DemoWindow()
window.show()
sys.exit(app.exec_())
上面的代码可以看出来,worker的run()要执行只能在timer_thread的start()被调用的时候自动执行,这样有点不灵活,有时候我们可能希望timer_thread的start()和worker的run()是分开的,可以通过信号来实现:
import time
import sys
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel, QApplication
class Worker(QObject):
# Worker 子类化QObject
signal_timer_cur_time = pyqtSignal(int)
def __init__(self):
super().__init__()
self.cur_time = 0
self.stop = False
def stop_worker(self):
# 通过标志位控制停止worker
self.stop = True
def run(self) -> None:
self.cur_time = 0
if self.stop:
self.stop = False
while not self.stop:
self.signal_timer_cur_time.emit(self.cur_time)
time.sleep(0.1)
self.cur_time += 1
class DemoWindow(QMainWindow):
signal_start_worker = pyqtSignal()
def __init__(self):
super().__init__()
self.setWindowTitle("测试窗口4")
self.resize(700, 500)
self.setCentralWidget(QWidget())
self.main_layout = QVBoxLayout()
self.centralWidget().setLayout(self.main_layout)
self.main_layout.addWidget(QLineEdit())
self.header_layout = QHBoxLayout()
self.btn_timer_start = QPushButton("扫描")
self.btn_timer_start.clicked.connect(self.timer_start)
self.header_layout.addWidget(self.btn_timer_start)
self.btn_timer_stop = QPushButton("停止")
self.btn_timer_stop.clicked.connect(self.timer_stop)
self.header_layout.addWidget(self.btn_timer_stop)
self.main_layout.addLayout(self.header_layout)
self.l_timer = QLabel()
self.main_layout.addWidget(self.l_timer)
# QThread不需要子类化了,直接实例化
self.timer_thread = QThread()
# 直接启动timer_thread线程
self.timer_thread.start()
# 实例化Worker
self.worker = Worker()
# 指定worker在timer_thread中执行
self.worker.moveToThread(self.timer_thread)
# 把signal_start_worker连接到worker.run
self.signal_start_worker.connect(self.worker.run)
self.worker.signal_timer_cur_time.connect(self.set_cur_time)
def closeEvent(self, a0) -> None:
self.worker.stop_worker()
self.timer_thread.terminate()
self.timer_thread.wait()
def set_cur_time(self, cur_time):
self.l_timer.setText(str(cur_time))
def timer_start(self):
if not self.timer_thread.isRunning():
self.timer_thread.start()
# signal_start_worker信号只要一emit,worker.run就会执行
self.signal_start_worker.emit()
def timer_stop(self):
# 停止worker
self.worker.stop_worker()
# 这里就不要quit了,否则timer_thread不是运行状态,在signal_start_worker信号emit之前就需要重新启动timer_thread
# self.timer_thread.quit()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DemoWindow()
window.show()
sys.exit(app.exec_())