赞
踩
在GUI程序中,单线程常常满足不了需求。这是因为,当程序需要执行一个非常耗时的操作(例如渲染、大量计算、数据传输等操作),那么整个界面则可能出现无法交互的情况(这是非常糟糕的现象,想象一下,你想点击一下按钮,可是鼠标却一直是转圈圈的状态)
一般来说,多线程技术涉及三种方法:
计时器模块QTimer;
多线程模块QThread;
事件处理
当我们要周期性进行某项操作时(如定时发送数据,定时获取传感器数据),则可以使用定时器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])
parent – PySide6.QtCore.QObject
属性
属性名 | 描述 |
---|---|
active | 布尔值,计时器是否正在运行 |
singleShot | 布尔值,计时器是否为单次计时器,默认为false |
interval | int类型,超时间隔,单位为毫秒。默认值为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())
单次计时器
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())
QThread类提供了一种独立于平台的方式来管理线程。
QThread 在 run() 中开始执行。默认情况下,run() 通过调用exec() 启动事件循环,并在线程内运行Qt事件循环。
构造函数
class PySide6.QtCore.QThread([parent=None])
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_())
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_())
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。