多线程,并发处理事务,提高效率;
多线程处理耗费时间的任务;
在Qt GUI 界面开发中使用多线程,这里总结两种方式:
-
基于threading模块
- 线程逻辑内部获取当前线程 threading.currentThread()
- 获取物理地址 threading.get_ident()
- 获取线程号 threading.get_native_id()
- os.kill(进程id, signal.SIGTERM) 杀死进程
- signal.pthread_kill(线程id, signal.SIGTERM) 杀死线程
-
基于QThread/QThreadPool
分别实现如下效果:
原理解析
子线程更新数据,主线程更新界面,即进度条状态。
使用python threading模块
from threading import Thread, Lock, RLock, Semaphore, BoundedSemaphore, Event
from PySide2.QtCore import QTimer, QDate, QTime, QDateTime, QThread, QThreadPool, Qt, QObject, Signal
from PySide2.QtWidgets import *
from PySide2.QtGui import QIcon, QFont
import PySide2
import logging
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s-%(message)s")
#
# 自定义信号
class Laufing(QObject):
notify = Signal(object)
# 自定义线程
class MyThread(Thread):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
Thread.__init__(self)
def run(self):
minField, maxField, sig = self.args
minVal = int(minField.text()) if minField.text() else None
maxVal = int(maxField.text()) if maxField.text() else None
# 通知 必须输入最小值、最大值
if minVal is None or maxVal is None:
sig.notify.emit(None)
else:
index = minVal
while index < maxVal:
index += 1
# QProgressBar 只能设置整数,自动计算百分比
# 这里待更新
pert = (index - minVal)/(maxVal - minVal) * 100
print("当前百分比:", pert)
sig.notify.emit(pert)
# 如下最少必须为1, 若为0.5 则无效,可采用msleep(500)
QThread.sleep(1)
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
# 设置窗口标题s
self.setWindowTitle("laufing")
self.setWindowIcon(QIcon("./imgs/dog.jpg"))
# 窗口居中
desk = QDesktopWidget()
width, height = desk.width(), desk.height()
self.resize(800, 600)
self.move(width//2 - self.width()//2, height//2 - self.height()//2)
self.laufing = Laufing()
self.laufing.notify.connect(self.updateUi)
#
self.setUi()
def setUi(self):
gridLayout = QGridLayout(self)
gridLayout.setContentsMargins(10, 10, 10, 10)
minLabel = QLabel("最小值")
minLabel.setStyleSheet("border: 1px solid red; border-radius: 10px; align:center;")
maxLabel = QLabel("最大值")
self.minField = QLineEdit()
self.minField.setPlaceholderText("输入最小值")
self.maxField = QLineEdit()
self.maxField.setPlaceholderText("输入最大值")
self.progress = QProgressBar()
btn = QPushButton("点击下载")
btn.clicked.connect(self.download)
gridLayout.addWidget(minLabel, 0, 0)
gridLayout.addWidget(self.minField, 0, 1)
gridLayout.addWidget(maxLabel, 1, 0)
gridLayout.addWidget(self.maxField, 1, 1)
gridLayout.addWidget(self.progress, 2, 0, columnSpan=2, alignment=Qt.AlignCenter)
gridLayout.addWidget(btn, 3, 0, columnSpan=2, alignment=Qt.AlignCenter)
gridLayout.setColumnStretch(2,10)
def download(self):
# 注意避免线程对象被垃圾回收
self.th = MyThread(self.minField, self.maxField, self.laufing)
# 设置守护线程,主线程退出,则不管子线程有没有执行完,子线程都退出
self.th.setDaemon(True)
self.th.start()
def updateUi(self, arg):
if arg is None:
mb = QMessageBox(self)
mb.setModal(True)
mb.setWindowTitle("提示")
mb.setText("<span style='color: red;'>必须输入最小值和最大值</span>")
mb.setTextFormat(Qt.TextFormat.RichText)
mb.setInformativeText("子标题")
confirm = mb.addButton("确定", QMessageBox.ButtonRole.YesRole)
cancle = mb.addButton("取消", QMessageBox.ButtonRole.NoRole)
# 按钮被点击
mb.buttonClicked.connect(lambda x : mb.hide() if x == confirm else None)
mb.show()
else:
self.progress.setValue(arg)
# 拖动窗口空白区域可移动窗口
def mousePressEvent(self, event:PySide2.QtGui.QMouseEvent):
self.originX = event.pos().x()
self.originY = event.pos().y()
def mouseMoveEvent(self, event:PySide2.QtGui.QMouseEvent):
self.move(event.screenPos().x() - self.originX, event.screenPos().y() - self.originY - 35)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = MyWidget()
win.show()
sys.exit(app.exec_())
基于QThread实现
- 重写run方法;
简单修改上述的线程类定义即可。 - 注意,QThread 线程运行期间不能被垃圾回收,否则报错;
# 防止线程对象在执行时,被垃圾回收或者强制kill,增加self引用
self.th = MyThread(self.minField, self.maxField, self.laufing)
# QThread 线程默认是守护线程
# run方法是在子线程中执行
# run执行后,再次调用start将不会进行任何处理
# run运行时,self.th.quit/exit 无法中断线程,只是等线程执行结束,再退出
# run 运行时, self.th.terminate() 可以立即中断子线程,但不安全
# run运行时,可以在主线程设置一个变量标识,子线程中运行时检测该变量,从而控制子线程的中断(注意加锁)。
# mutex = QMutex()
# mutex.lock()/unlock()
# QThread子线程的信号
self.th.started.connect(func) # 子线程正常开始后
self.th.finished.connect(self.th.deleteLater()) # 子线程正常结束后
# 正常结束后,一般使用deleteLater释放内存,析构对象
使用QThreadPool实现
QThreadPool 官方文档
结合QRunnable使用
from PySide2.QtCore import Qt, QThreadPool, QRunnable, QThread
class Task(QRunnable):
def run(self):
i = 0
while i < 10:
print(i)
QThread.sleep(1)
i += 1
self.th_pool = QThreadPool(self)
self.th_pool.setMaxThreadCount(10)
self.task = Task()
self.th_pool.start(self.task)
# 取消队列中尚未执行的任务
# th_pool.cancle(task) 已弃用
th_pool.tryTake(task)
# 正在执行的任务,不能强制terminate 或者kill
# 只能在task->run的逻辑内部判断flag 来主动return