PyQt5自学记录(1)——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)
A触发信号事件
B 信号clicked
C 接收信号做出相应操作

通过鼠标点击(事件)触发信号,通过信号的传递控制相应的操作是否进行。自定义信号同样满足这种机制,自定义信号的机制与主要函数如下图:

A触发信号事件emit
B 声明信号 pyqtSignal
C 接收信号做出相应操作connect

首先要声明一个信号,通过信号名 = 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

发布了3 篇原创文章 · 获赞 5 · 访问量 183

猜你喜欢

转载自blog.csdn.net/qq_40784418/article/details/105398870
今日推荐