PyQt5 系统化学习: 多线程

19 多线程

《PyQt5快速开发与实战》学习笔记。

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

19.1 QTimer

如果要在应用程序中周期性地进行某项操作,比如周期性地检测主机的 CPU 值,则需要用到 QTimer(定时器),QTimer 类提供了重复的和单次的定时器。要使用定时器,需要先创建一个 QTimer 实例,将其 timeout 信号连接到相应的 slot,并调用 start()。然后,定时器会以恒定的间隔发出 timeout 信号。当窗口控件收到 timeout 信号后,它就会停止这个定时器。这是在图形用户界面中实现复杂工作的一个典型方法,随着技术的进步,多线程在越来越多的平台上被使用,最终 QTimer 对象会被线程所替代。

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().__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__":
    app = QApplication(sys.argv)
    form = WinForm()
    form.show()
    sys.exit(app.exec_())

效果如下:

演示弹出一个窗口,然后这个窗口在 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 PyQT,窗口会在10秒后消失!")
    label.setWindowFlags(Qt.SplashScreen | Qt.FramelessWindowHint)
    label.show()

    # 设置10s后自动退出
    QTimer.singleShot(10000, app.quit)
    sys.exit(app.exec_())

效果如下:

弹出的窗口将在10秒后消失,模仿程序的启动画面。将弹出的窗口设置为无边框。

19.2 QThread

QThread 是 Qt 线程类中最核心的底层类。由于 PyQt5 的跨平台特性,QThread 要隐藏所有与平台相关的代码。要使用 QThread 开始一个线程,可以创建它的一个子类,然后覆盖其 QThread.run() 函数。

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__":
    app = QApplication(sys.argv)
    demo = MainWidget()
    demo.show()
    sys.exit(app.exec_())

效果如下:

分离 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_())

效果:

19.3 事件处理

PyQt5 为事件处理提供了两种机制:高级的信号与槽机制,以及低级的事件处理程序。本节只介绍低级的事件处理程序,即 processEvents() 函数的使用方法,它的作用是处理事件,简单地说,就是刷新页面。
对于执行很耗时的程序来说,由于 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_())

效果:

你可能感兴趣的:(PyQt5 系统化学习: 多线程)