当前位置:   article > 正文

PySide6————多线程技术(QTimer、QThread、事件处理)_pyside6 qthread

pyside6 qthread

GUI程序中,单线程常常满足不了需求。这是因为,当程序需要执行一个非常耗时的操作(例如渲染、大量计算、数据传输等操作),那么整个界面则可能出现无法交互的情况(这是非常糟糕的现象,想象一下,你想点击一下按钮,可是鼠标却一直是转圈圈的状态)

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

  • 计时器模块QTimer;

  • 多线程模块QThread;

  • 事件处理

QTimer计时器

当我们要周期性进行某项操作时(如定时发送数据,定时获取传感器数据),则可以使用定时器QTimer,该类提供了重复定时器和单次定时器。

可以在具有事件循环的线程中使用 QTimer;要在非GUI线程启动事件循环,可以使用 exec() 。Qt使用计时器的 ”线程相关性“ 来确定哪一个线程发出 timeout() 信号,因而必须在该线程中开启和关闭计时器,而不能从另一个线程启动或关闭。

==作为一种特殊情况,超时为0的QTimer将尽快超时,即尽快反复执行(一旦处理完窗口系统的事件队列中的所有事件,超时间隔为0的QTimer就会超时)。==这种情况下槽函数代码应该很简单,能够快速返回,并在完成所有工作后立即停止计时器。这是在GUI程序中实现繁重工作的传统方式,但随着多线程的广泛使用,零毫秒计时器将被 QThread 代替。

计时器的精确度取决于底层操作系统和硬件。大多数平台支持1毫秒的分辨率,尽管在许多现实世界的情况下,计时器的精度不会等于这个分辨率。如果系统繁忙或无法提供所要求的精度,则所有类型的计时器都可能比预期的晚超时,在这种超时溢出的情况下,即使多个超时已经过期,Qt也只会发出一次timeout() ,然后将恢复原始间隔。

定时器的实现依赖的是CPU时钟中断,时钟中断的精度就决定定时器精度的极限。一个时钟中断源如何实现多个定时器呢?对于内核,简单来说就是用特定的数据结构管理众多的定时器,在时钟中断处理中判断哪些定时器超时,然后执行超时处理动作。而用户空间程序不直接感知CPU时钟中断,通过感知内核的信号、IO事件、调度,间接依赖时钟中断。用软件来实现动态定时器常用数据结构有:时间轮、最小堆和红黑树。

另一种实现计时器的方法是继承QObject类,并重写 timerEvent() 事件处理器,然后调用 startTimer() 函数。具体 细节可以查看:QTimer - Qt for Python

构造函数

class PySide6.QtCore.QTimer([parent=None])
  • 1

parent – PySide6.QtCore.QObject

属性

属性名描述
active布尔值,计时器是否正在运行
singleShot布尔值,计时器是否为单次计时器,默认为false
intervalint类型,超时间隔,单位为毫秒。默认值为0

函数

函数名描述
start()
start(msec)
开启计时器,如果singleShot为true,则计时器将仅激活一次。
stop()关闭计时器
timeout()计时器超时时会发出此信号

重复计时器

import sys

from PySide6.QtCore import QDateTime, QTimer
from PySide6.QtGui import Qt
from PySide6.QtWidgets import QWidget, QApplication, QLabel, QPushButton, QGridLayout


class LearnQThread(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Learn QThread')
        self.resize(640, 480)

        self.label = QLabel(self)
        self.label.setAlignment(Qt.AlignCenter)
        self.btnStart = QPushButton('start')
        self.btnEnd = QPushButton('end')
        layout = QGridLayout()
        self.timer = QTimer()

        self.timer.timeout.connect(self.showtime)

        layout.addWidget(self.label, 0, 0, 1, 2)
        layout.addWidget(self.btnStart, 1, 0)
        layout.addWidget(self.btnEnd, 1, 1)

        self.btnStart.clicked.connect(self.StartTimer)
        self.btnEnd.clicked.connect(self.endTimer)
        self.setLayout(layout)

    """
    
    QTimer定时处理的任务
    
    """

    def showtime(self):
        # 获取当前时间
        time = QDateTime.currentDateTime()
        # 将当前时间转换成字符串类型
        timeDisplay = time.toString('yyyy-MM-dd hh:mm:ss dddd')

        self.label.setText(timeDisplay)

    def StartTimer(self):
        # 开启定时器,执行时间/任务的频率是100ms
        self.timer.start(100)
        self.btnStart.setEnabled(False)
        self.btnEnd.setEnabled(True)

    def endTimer(self):
        self.timer.stop()
        self.label.clear()
        self.btnStart.setEnabled(True)
        self.btnEnd.setEnabled(False)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    wid = LearnQThread()
    wid.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

单次计时器

import sys
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QApplication, QWidget, QLabel,QGridLayout


class LearnQTimer(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(320,240)
        self.label = QLabel(self)
        layout = QGridLayout()
        layout.addWidget(self.label)
        self.setLayout(layout)
        self.label.setText('close ui')
        self.label.setAlignment(Qt.AlignCenter)
        QTimer.singleShot(5000, self.close)			# 5秒后自动关闭窗口


if __name__ == '__main__':
    app = QApplication(sys.argv)
    wid = LearnQTimer()
    wid.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

QThread多线程

QThread类提供了一种独立于平台的方式来管理线程。

QThread 在 run() 中开始执行。默认情况下,run() 通过调用exec() 启动事件循环,并在线程内运行Qt事件循环

构造函数

class PySide6.QtCore.QThread([parent=None])
  • 1

parent – PySide6.QtCore.QObject

在调用start() 之前,线程不会开始执行。

函数

函数名描述
exec()run() 会调用此函数,有必要调用此函数来启动事件处理。这只能在线程本身内调用,即当它是当前线程时。
exit([retcode=0])线程的事件循环退出并返回一个int型整数。
finished()当发出此信号时,事件循环已经停止运行,线程中将不再处理任何事件。这个信号可以连接到deleteLater(),以释放该线程中的对象。
run()在调用 start() 之后,新创建的线程将自动调用此函数。默认实现只调用exec()
start([priority=QThread.Priority.InheritPriority])内部通过调用 run() 开始执行线程
started()执行 run() 之前发出此信号
import sys
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *

global sec
sec = 0


class WorkThread(QThread):
    trigger = Signal()


    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
  • 69

事件处理

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

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

因此QApplication.processEvents()的使用方法就是,在主函数执行耗时操作的地方,加入QApplication.processEvents()。

from PySide6.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
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/854079
推荐阅读
相关标签
  

闽ICP备14008679号