• PyQt5快速开发与实战 5.3 多线程


    PyQt5快速开发与实战

    5. 第5章 PyQt5 高级界面控件

    5.3 多线程

    一般情况下,应用程序都是单线程运行的,但是对于GUI程序来说,单线程有时候满足不了需求。比如,如果需要执行一个特别耗时的操作,在执行过程中整个程序就会卡顿,这时候用户可能以为程序出错,就把程序关闭了;或者Windows系统也认为程序运行出错,自动关闭了程序。要解决这种问题就涉及多线程的知识。

    一般来说,多线程技术涉及三种方法:

    • 使用计时器模块QTimer;
    • 使用多线程模块QThread;
    • 使用事件处理的功能。
    5.3.1 QTimer

    如果要在应用程序中周期性地进行某项操作,比如周期性地检测主机的 CPU值,则需要用到QTimer(定时器),QTimer类提供了重复的和单次的定时器。要使用定时器,需要先创建一个QTimer实例,将其timeout信号连接到相应的槽,并调用start()。然后,定时器会以恒定的间隔发出 timeout信号。

    当窗口控件收到timeout信号后,它就会停止这个定时器。这是在图形用户界面中实现复杂工作的一个典型方法,随着技术的进步,多线程在越来越多的平台上被使用,最终QTimer对象会被线程所替代。

    QTimer类中的常用方法

    方法描述
    start(milliseconds)启动或重新启动定时器,时间间隔为毫秒。如果定时器已经运行,它将被停止并重新启动。如果singleShot信号为真,定时器将仅被激活一次
    Stop()停止定时器

    QTimer类中的常用信号:

    信号描述
    singleShot在给定的时间间隔后调用一个槽函数时发射此信号
    timeout当定时器超时时发射此信号

    案例

    from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QListWidget, QGridLayout, QLabel
    from PyQt5.QtCore import QTimer, QDateTime
    import sys
    
    
    class WinForm(QWidget):
    
        def __init__(self, parent=None):
            super(WinForm, self).__init__(parent)
            self.setWindowTitle("QTimer demo")
            self.listFile = QListWidget()
            self.label = QLabel('显示当前时间')
            self.startBtn = QPushButton('开始')
            self.endBtn = QPushButton('结束')
            layout = QGridLayout(self)
    
            # 初始化一个定时器
            self.timer = QTimer(self)
            # showTime()方法
            self.timer.timeout.connect(self.showTime)
    
            layout.addWidget(self.label, 0, 0, 1, 2)
            layout.addWidget(self.startBtn, 1, 0)
            layout.addWidget(self.endBtn, 1, 1)
    
            self.startBtn.clicked.connect(self.startTimer)
            self.endBtn.clicked.connect(self.endTimer)
    
            self.setLayout(layout)
    
        def showTime(self):
            # 获取系统现在的时间
            time = QDateTime.currentDateTime()
            # 设置系统时间显示格式
            timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd");
            # 在标签上显示时间
            self.label.setText(timeDisplay)
    
        def startTimer(self):
            # 设置计时间隔并启动
            self.timer.start(1000)
            self.startBtn.setEnabled(False)
            self.endBtn.setEnabled(True)
    
        def endTimer(self):
            self.timer.stop()
            self.startBtn.setEnabled(True)
            self.endBtn.setEnabled(False)
    
    if __name__ == '__main__':
        from pyqt5_plugins.examples.exampleqmlitem import QtCore
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
        app = QApplication(sys.argv)
        win = WinForm()
        win.show()
        sys.exit(app.exec_())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    在这里插入图片描述

    案例——弹出一个窗口,10秒后消失

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        label = QLabel("Hello,10秒!")
    
        label.setWindowFlags(Qt.SplashScreen | Qt.FramelessWindowHint)
        label.show()
    
        QTimer.singleShot(10000,app.quit)
        sys.exit(app.exec_())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    5.3.2 QThread

    QThread是Qt线程类中最核心的底层类。由于PyQt的跨平台特性,QThread要隐藏所有与平台相关的代码。

    要使用QThread开始一个线程,可以创建它的一个子类然后覆盖其QThread.run()函数。

    from PyQt5.QtCore import QThread
    
    class Thread(QThread):
        def __init__(self):
            super(Thread, self).__init__()
    
        def run(self):
            pass
        
    thread = Thread()
    thread.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在使用线程时可以直接得到Thread实例,调用其start()函数即可启动线程。线程启动之后,会自动调用其实现的run方法,该方法就是线程的执行函数。

    业务的线程任务就写在 run()函数中,当run()退出之后线程基本就结束了。QThread有started和 finished信号,可以为这两个信号指定槽函数,在线程启动和结束时执行一段代码进行资源的初始化和释放操作。更灵活的使用方法是,在自定义的QThread实例中自定义信号,并将信号连接到指定的槽函数,当满足一定的业务条件后发射此信号。

    1. QThread类中的常用方法和信号

      方法:

      方法描述
      start()启动线程
      wait()阻止线程,直到满足如下条件之一:与此QThread对象关联的线程已完成执行(即从 run()返回时)。如果线程完成执行,此函数将返回True:如果线程尚未启动,此函数也返回True;等待时间的单位是亳秒。如果时间是ULONG_MAX(默认值),则等待,永远不会超时(线程必须从 run()返回);如果等待超时,此函数将返回False
      sleep()强制当前线程睡眠

      信号

      信号描述
      started在开始执行run()函数之前,从相关线程发射此信号
      finished当程序完成业务逻辑时,从相关线程发射此信号
    2. QThread实例

      当在窗口中显示的数据比较简单时,可以把读取数据的业务逻辑放在窗口的初始化代码中,但如果读取数据的时间比较长,比如网络请求的时间比较长,则可以把这部分逻辑放在QThread线程中,实现界面的数据显示和数据读取的分离,满足MVC设计模式的要求。

      from PyQt5.QtCore import *
      from PyQt5.QtGui import *
      from PyQt5.QtWidgets import *
      import sys
      
      
      class MainWidget(QWidget):
          def __init__(self, parent=None):
              super(MainWidget, self).__init__(parent)
              self.setWindowTitle("QThread 例子")
              self.thread = Worker()
              self.listFile = QListWidget()
              self.btnStart = QPushButton('开始')
              layout = QGridLayout(self)
              layout.addWidget(self.listFile, 0, 0, 1, 2)
              layout.addWidget(self.btnStart, 1, 1)
              self.btnStart.clicked.connect(self.slotStart)
              self.thread.sinOut.connect(self.slotAdd)
      
          def slotAdd(self, file_inf):
              self.listFile.addItem(file_inf)
      
          def slotStart(self):
              self.btnStart.setEnabled(False)
              self.thread.start()
      
      
      class Worker(QThread):
          sinOut = pyqtSignal(str)
      
          def __init__(self, parent=None):
              super(Worker, self).__init__(parent)
              self.working = True
              self.num = 0
      
          def __del__(self):
              self.working = False
              self.wait()
      
          def run(self):
              while self.working == True:
                  file_str = 'File index {0}'.format(self.num)
                  self.num += 1
                  # 发出信号
                  self.sinOut.emit(file_str)
                  # 线程休眠2秒
                  self.sleep(2)
      
      if __name__ == '__main__':
          from pyqt5_plugins.examples.exampleqmlitem import QtCore
          QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
          app = QApplication(sys.argv)
          win = MainWidget()
          win.show()
          sys.exit(app.exec_())
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55

      在这里插入图片描述

      模拟长时间读取数据导致界面卡死

      import sys
      from PyQt5.QtCore import *
      from PyQt5.QtGui import *
      from PyQt5.QtWidgets import *
      
      global sec
      sec=0
      
      def setTime():
         global  sec
         sec+=1
         # LED显示数字+1
         lcdNumber.display(sec)          
      
      def work():
         # 计时器每秒计数
         timer.start(1000)              
         for i in range(2000000000):
            pass
         
         timer.stop()
      
      if __name__ == "__main__":     
         app = QApplication(sys.argv) 
         top = QWidget()
         top.resize(300,120)
          
         # 垂直布局类QVBoxLayout
         layout = QVBoxLayout(top)    
         # 加个显示屏    
         lcdNumber = QLCDNumber()             
         layout.addWidget(lcdNumber)
         button=QPushButton("测试")
         layout.addWidget(button)
      
         timer = QTimer()
         # 每次计时结束,触发setTime
         timer.timeout.connect(setTime)      
         button.clicked.connect(work)  
          
         top.show()
         sys.exit(app.exec_())
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42

      在这里插入图片描述

    案例—— 分离UI主线程和工作线程

    import sys
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    
    global sec
    sec = 0
    
    
    class WorkThread(QThread):
        trigger = pyqtSignal()
    
        def __int__(self):
            super(WorkThread, self).__init__()
    
        def run(self):
            for i in range(2000000000):
                pass
    
            # 循环完毕后发出信号
            self.trigger.emit()
    
    
    def countTime():
        global sec
        sec += 1
        # LED显示数字+1
        lcdNumber.display(sec)
    
    
    def work():
        # 计时器每秒计数
        timer.start(1000)
        # 计时开始
        workThread.start()
        # 当获得循环完毕的信号时,停止计数
        workThread.trigger.connect(timeStop)
    
    
    def timeStop():
        timer.stop()
        print("运行结束用时", lcdNumber.value())
        global sec
        sec = 0
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        top = QWidget()
        top.resize(300, 120)
    
        # 垂直布局类QVBoxLayout
        layout = QVBoxLayout(top)
        # 加个显示屏
        lcdNumber = QLCDNumber()
        layout.addWidget(lcdNumber)
        button = QPushButton("测试")
        layout.addWidget(button)
    
        timer = QTimer()
        workThread = WorkThread()
    
        button.clicked.connect(work)
        # 每次计时结束,触发 countTime
        timer.timeout.connect(countTime)
    
        top.show()
        sys.exit(app.exec_())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    在这里插入图片描述

    5.3.3 事件处理

    PyQt为事件处理提供了两种机制:高级的信号与槽机制,以及低级的事件处理程序。这里只介绍低级的事件处理程序,即proccssEvents()函数的使用方法,它的作用是处理事件,简单地说,就是刷新页面。

    对于执行很耗时的程序来说,由于 PyQt需要等待程序执行完毕才能进行下一步,这个过程表现在界面上就是卡顿;而如果在执行这个耗时程序时不断地运行QApplication.processEvents(),那么就可以实现一边执行耗时程序,一边刷新页面的功能,给人的感觉就是程序运行很流畅。因此QApplication.processEvents()的使用方法就是,在主函数执行耗时操作的地方,加入 QApplication.processEvents()。

    案例——演示实时刷新页面

    from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QListWidget, QGridLayout
    import sys
    import time
    
    
    class WinForm(QWidget):
    
        def __init__(self, parent=None):
            super(WinForm, self).__init__(parent)
            self.setWindowTitle("实时刷新界面例子")
            self.listFile = QListWidget()
            self.btnStart = QPushButton('开始')
            layout = QGridLayout(self)
            layout.addWidget(self.listFile, 0, 0, 1, 2)
            layout.addWidget(self.btnStart, 1, 1)
            self.btnStart.clicked.connect(self.slotAdd)
            self.setLayout(layout)
    
        def slotAdd(self):
            for n in range(10):
                str_n = 'File index {0}'.format(n)
                self.listFile.addItem(str_n)
                QApplication.processEvents()
                time.sleep(1)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        form = WinForm()
        form.show()
        sys.exit(app.exec_())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    个人感觉这更像卡出来的

    在这里插入图片描述

  • 相关阅读:
    zabbix 自动发现与自动注册(接上章补充)
    新威凌将开启申购:2022年第三季度业绩下滑,陈志强为董事长
    跟k8s工作负载Deployments的缘起缘灭
    线程池框架-Spring线程池
    Python 实现http server接收mutipart/form-data文件 方法1
    人大金仓助力国家电网调度中心培养国产数据库专家人才
    JS中? ?和??=和?.和 ||的区别
    机器学习數據降維之主成分分析(PCA)
    QT c++ 将浮点数数组转换成 QByteArray
    需求分析简介
  • 原文地址:https://blog.csdn.net/weixin_44226181/article/details/126035783