简单介绍
在pyqt5中,如果不是特别复杂的程序,不建议手动操作线程,因为有时候不知道会发生什么致命的bug,在qt中操作线程的简单说明:
QWaitCondition()用于多线程同步,一个线程调用QWaitCondition.wait()阻塞等待, 直到另外一个线程调用QWaitCondition.wake()唤醒才继续往下执行 QMutex():是锁对象
线程执行的时候需要先上锁,并在运行的时候,定义一个判断标志,如果该标志触发就执行线程的挂起,直到再次唤醒。
案例一
主程序启动一个多线程,同时启动一个QDialog弹窗,将线程执行的结果交给dialog做展示,为了方便观察,将线程的执行结果在主程序界面跟dialog界面的QListWidget同时展示,除此之外,在dialog弹窗关闭后,也要触发线程关闭动作。
代码如下:
import sys
import time
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QWaitCondition, QMutex
from PyQt5.QtWidgets import QWidget, QApplication, QDialog, QHBoxLayout, QListWidget
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(Form)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setSpacing(30)
self.verticalLayout.setObjectName("verticalLayout")
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.start_btn = QtWidgets.QPushButton(Form)
self.start_btn.setObjectName("start_btn")
self.verticalLayout.addWidget(self.start_btn)
self.pause_btn = QtWidgets.QPushButton(Form)
self.pause_btn.setObjectName("pause_btn")
self.verticalLayout.addWidget(self.pause_btn)
self.resume_btn = QtWidgets.QPushButton(Form)
self.resume_btn.setObjectName("resume_btn")
self.verticalLayout.addWidget(self.resume_btn)
self.stop_btn = QtWidgets.QPushButton(Form)
self.stop_btn.setObjectName("stop_btn")
self.verticalLayout.addWidget(self.stop_btn)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem1)
self.horizontalLayout.addLayout(self.verticalLayout)
self.listWidget = QtWidgets.QListWidget(Form)
self.listWidget.setObjectName("listWidget")
self.horizontalLayout.addWidget(self.listWidget)
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.start_btn.setText(_translate("Form", "开始"))
self.pause_btn.setText(_translate("Form", "暂停"))
self.resume_btn.setText(_translate("Form", "唤醒"))
self.stop_btn.setText(_translate("Form", "停止"))
# 主程序点击后,会有弹窗
class Show_msg(QDialog):
quit_trig = pyqtSignal()
def __init__(self, parent=None):
super(Show_msg, self).__init__(parent)
self.initUi()
def initUi(self):
hLayout = QHBoxLayout()
self.list_msg = QListWidget()
hLayout.addWidget(self.list_msg)
self.setLayout(hLayout)
self.setWindowTitle('弹窗显示')
def msg_setValue(self, msg):
self.list_msg.addItem(msg)
def closeEvent(self, event):
self.quit_trig.emit() # 关闭弹窗时,发出关闭信号,让主程序的进程关闭
event.accept()
pass
class ThreadStopTest(QWidget, Ui_Form):
def __init__(self):
super(ThreadStopTest, self).__init__()
self.setupUi(self)
self.setWindowTitle('主程序窗口')
self.handle()
def handle(self):
self.start_btn.clicked.connect(self.start_thread)
self.stop_btn.clicked.connect(self.stop_thread)
self.pause_btn.clicked.connect(self.pause_thread)
self.resume_btn.clicked.connect(self.resume_thread)
def start_thread(self):
self.msg_dialog = Show_msg(self) # 点击开始按钮后加载弹窗
self.msg_dialog.show() # 显示弹窗
self.msg_dialog.quit_trig.connect(self.stop_thread) # 将弹窗中的退出信号绑定到退出线程的方法上
self.t = My_thread() # 实例化一个线程,i并启动
self.t.num_trig.connect(self.setValue) # 线程信号绑定到主程序listWidget上
self.t.num_trig.connect(self.msg_dialog.msg_setValue) # 线程信号绑定到弹窗中的listWidget上
self.t.start()
# 暂停线程
def pause_thread(self):
if self.t:
self.t.pause()
# 唤醒线程
def resume_thread(self):
if self.t:
self.t.resume()
# 停止线程
def stop_thread(self):
if self.t:
self.t.terminate()
self.t = None
self.msg_dialog.close() # 停止线程时,关闭弹窗
else:
self.listWidget.addItem('线程不存在')
def setValue(self, v):
self.listWidget.addItem(v)
class My_thread(QThread):
num_trig = pyqtSignal(str)
def __init__(self):
super(My_thread, self).__init__()
'''
QWaitCondition()用于多线程同步,一个线程调用QWaitCondition.wait()阻塞等待,
直到另外一个线程调用QWaitCondition.wake()唤醒才继续往下执行
QMutex():是锁对象
'''
self._isPause = False
self.cond = QWaitCondition()
self.mutex = QMutex()
def run(self) -> None:
a = 0
while True:
self.mutex.lock() # 上锁
if self._isPause:
self.cond.wait(self.mutex)
self.num_trig.emit(f'item{a}')
a += 1
QThread.sleep(2)
self.mutex.unlock() # 解锁
# 线程暂停
def pause(self):
self._isPause = True
# 线程恢复
def resume(self):
self._isPause = False
self.cond.wakeAll()
if __name__ == '__main__':
# PyQt5高清屏幕自适应设置,以及让添加的高清图标显示清晰,不然designer导入的图标在程序加载时会特别模糊
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
main_win = ThreadStopTest()
main_win.show()
sys.exit(app.exec_())
案例二
主界面定义一个进度条,两个按钮,一个休眠,一个唤醒,点击休眠,线程挂起,点击恢复,线程被唤起。
案例二引用自:04-QThread子线程的创建方式与线程挂起、唤醒 | PyQt - _vscode
from PyQt5.QtCore import QThread, QWaitCondition, QMutex, pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QProgressBar, QApplication
class Thread(QThread):
valueChange = pyqtSignal(int)
def __init__(self, *args, **kwargs):
super(Thread, self).__init__(*args, **kwargs)
self._isPause = False
self._value = 0
self.cond = QWaitCondition()
self.mutex = QMutex()
def pause(self):
self._isPause = True
def resume(self):
self._isPause = False
self.cond.wakeAll()
def run(self):
while 1:
self.mutex.lock()
if self._isPause:
self.cond.wait(self.mutex)
if self._value > 100:
self._value = 0
self._value += 1
self.valueChange.emit(self._value)
self.msleep(100)
self.mutex.unlock()
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
layout = QVBoxLayout(self)
self.progressBar = QProgressBar(self)
layout.addWidget(self.progressBar)
layout.addWidget(QPushButton('休眠', self, clicked=self.doWait))
layout.addWidget(QPushButton('唤醒', self, clicked=self.doWake))
self.t = Thread(self)
self.t.valueChange.connect(self.progressBar.setValue)
self.t.start()
def doWait(self):
self.t.pause()
def doWake(self):
self.t.resume()
if __name__ == '__main__':
import sys
# import cgitb
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
# cgitb.enable(format='text')
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())