PyQt5自学记录——PyQt5多线程实现详解
Posted 胖大海pyh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyQt5自学记录——PyQt5多线程实现详解相关的知识,希望对你有一定的参考价值。
PyQt5自学记录(1)——PyQt5中多线程实现详解
最近想用PyQt5完成图像识别的一个GUI系统,在调用算法模型进行识别的时候,界面会卡住没有反应,所以想学习一下多线程解决这个问题。然后。。。发现没有基础学习来确实挺难,幸运地是最终实现了多线程,记录一下学习过程。如有错误,希望指正,一起进步。
进程和线程
线程是一个轻负荷的子进程,是最小的处理单元。线程被包含在进程之中,是进程中的实际运作单位。一个进程可以并发多个线程,每条线程同时执行不同的任务,并且,线程是独立的,一个线程发生错误,不影响其他线程正常执行。
进程是指正在运行中的应用程序。每个进程都有自己独立的内存空间,当用户启动一个进程时,操作系统就会为该进程分配一个独立的内存空间,让应用程序在独立内存中运行
在实现多任务中,这篇博客中写到,要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。在Windows系统下,多线程的效率要比多进程的效率要高。
PyQt5的多线程实现
PyQt5中自定义信号
在介绍PyQt5的多线程实现之前,先介绍一下PyQt5中自定义信号的方法。
我们先来看一下PyQt5中自带的信号clicked的实现机制:
self.Button_Start.clicked.connect(self.Start)
通过鼠标点击(事件)触发信号,通过信号的传递控制相应的操作是否进行。自定义信号同样满足这种机制,自定义信号的机制与主要函数如下图:
首先要声明一个信号,通过信号名 = pyqtSignal(类型)实现
finishSignal = pyqtSignal(str) # 信号类型:str
第二步要明确信号控制的操作,也就是槽函数。信号发出之后我们希望进行的操作,通过信号所在类实例.信号名.connect(槽函数)实现。
self.thread.finishSignal.connect(self.Change) # 信号挂接到槽:update
def Change(self, msg):
print(msg)
self.label.setText(str(msg))
最后一步,明确信号的触发机制,我们确定了信号以及信号控制的槽函数,那么什么时候发出这个消息呢,这就需要信号的触发机制,通过信号名.emit(信号内容)实现。
self.finishSignal.emit(str(i)) # 发射信号
总结:emit(i)函数触发自定义信号将参数i发送给槽函数,槽函数接受信号,执行指定操作。
多线程实现
理解了自定义信号PyQt5的多线程也就不难了,一个简单的例子看一下他的实现过程:主线程执行GUI界面,子线程用定时器读秒实时显示在主线程的GUI界面中。
通过QtDesigner设计GUI界面,实现逻辑界面和显示界面的分开,对新手来说无疑是一个福音。首先看一下整体的一个结构
therad.py是线程类文件,thread.ui是QtDesigner生成的UI文件,Thread_gui.py是由UI文件转换来的界面py文件,Thread_win.py是编写的逻辑文件。下面详细介绍多线程实现过程。
第一步:利用QtDesigner设计显示界面。并转换为py文件,也就是图中的thread.ui和Thread_gui.py文件。生成的Thread_gui.py不需要做任何更改,在逻辑文件中设置相关逻辑操作,Thread_gui.py代码如下
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'untitled.ui'
#
# Created by: PyQt5 UI code generator 5.13.0
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(436, 337)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.Button_Start = QtWidgets.QPushButton(self.centralwidget)
self.Button_Start.setGeometry(QtCore.QRect(60, 200, 93, 28))
self.Button_Start.setObjectName("pushButton")
self.Button_Stop = QtWidgets.QPushButton(self.centralwidget)
self.Button_Stop.setGeometry(QtCore.QRect(250, 200, 93, 28))
self.Button_Stop.setObjectName("pushButton_2")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(80, 90, 121, 41))
self.label.setStyleSheet("background-color: rgb(85, 255, 255);")
self.label.setText("")
self.label.setObjectName("label")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 436, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.Button_Start.setText(_translate("MainWindow", "Start"))
self.Button_Stop.setText(_translate("MainWindow", "Stop"))
第二步:编写线程类文件,根据任务需求,在子线程中执行计时操作,并把事件返回到主线程中。定义一个线程类:首先要自定义信号,并把希望在线程中执行的操作写入类中run( )函数中。线程类文件therad.py代码如下
# -*- coding: utf-8 -*-
import time
from PyQt5.QtCore import QThread, pyqtSignal
#定义一个线程类
class New_Thread(QThread):
#自定义信号声明
# 使用自定义信号和UI主线程通讯,参数是发送信号时附带参数的数据类型,可以是str、int、list等
finishSignal = pyqtSignal(str)
# 带一个参数t
def __init__(self, t,parent=None):
super(New_Thread, self).__init__(parent)
self.t = t
#run函数是子线程中的操作,线程启动后开始执行
def run(self):
for i in range(self.t):
time.sleep(1)
#发射自定义信号
#通过emit函数将参数i传递给主线程,触发自定义信号
self.finishSignal.emit(str(i)) # 注意这里与_signal = pyqtSignal(str)中的类型相同
第三步:编写逻辑代码,首先要到导入前面两个文件,定义一个类包含相关逻辑操作。Thread_win.py代码如下
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
from Thread.Thread_gui import *
from Thread.therad import New_Thread
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
# 绑定按钮点击事件
self.Button_Start.clicked.connect(self.Start)
self.Button_Stop.clicked.connect(self.Stop)
def Stop(self):
print('End')
self.thread.terminate() # 终止线程
def Start(self):
print('Start clicked.')
self.thread = New_Thread(t=100) #实例化一个线程,参数t设置为100
# 将线程thread的信号finishSignal和UI主线程中的槽函数Change进行连接
self.thread.finishSignal.connect(self.Change)
# 启动线程,执行线程类中run函数
self.thread.start()
#接受通过emit传来的信息,执行相应操作
def Change(self, msg):
print(msg)
self.label.setText(str(msg))
if __name__ == '__main__':
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
通过以上三个文件就可以实现多线程操作了,运行Thread_win.py函数就可以实现。
总结
(1)学习多线程首先要了解PyQt5中的自定义信号:包括三部分
声明信号 :信号名 = pyqtSignal(数据类型)
信号与槽函数连接:信号名.connect(槽函数)
自定义信号的触发:信号名.emit(参数) 注意:参数数据类型要和声明信号 的类型保持一致!!!
(2)实现多线程时:首先要创建一个线程类,线程类中设置自定义信号,通过emit将信号发出(传递了参数),再由UI界面的槽函数接受,执行相关操作。
参考
https://blog.csdn.net/foreveronly/article/details/82453697
https://blog.csdn.net/Mr_Zing/article/details/46945011
以上是关于PyQt5自学记录——PyQt5多线程实现详解的主要内容,如果未能解决你的问题,请参考以下文章
如何使用python3.5.2+pyqt5编写无阻塞多线程GUI
Python3.5+PyQt5多线程+itchat实现微信防撤回桌面版代码