PySide6 中的 QTimer 是用于实现定时任务的核心类,支持单次触发、重复触发、动态调整间隔等功能,广泛应用于界面刷新、定时检测、延时执行等场景。
一、核心概念
- 定时器本质 :
QTimer依赖 Qt 的事件循环(QApplication.exec()),通过向事件队列发送定时事件实现触发,因此必须在事件循环运行的环境中使用。 - 核心参数 :
- 间隔(interval):定时触发的时间间隔,单位为毫秒(ms),默认 0(立即触发,但需等待事件循环)。
- 单次触发(singleShot):是否仅触发一次(默认重复触发)。
- 精度:默认精度为 1ms,但受系统调度影响,实际间隔可能略有偏差(毫秒级)。
二、基础用法
1. 基本使用步骤
- 导入
QTimer类; - 创建
QTimer实例,绑定触发信号(timeout)到槽函数; - 设置间隔 / 单次触发属性;
- 启动定时器。
示例 1:重复触发定时器(定时刷新界面)
import sys
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
from PySide6.QtCore import QTimer, Qt
class TimerDemo(QWidget):
def __init__(self):
super().__init__()
self.count = 0 # 计数变量
self.init_ui()
self.init_timer()
def init_ui(self):
self.setWindowTitle("QTimer 重复触发示例")
self.label = QLabel("计数:0", self)
layout = QVBoxLayout()
layout.addWidget(self.label)
self.setLayout(layout)
def init_timer(self):
# 1. 创建定时器实例
self.timer = QTimer(self) # 绑定父对象,自动管理内存
# 2. 设置间隔(1000ms = 1秒)
self.timer.setInterval(1000)
# 3. 绑定timeout信号到槽函数
self.timer.timeout.connect(self.update_count)
# 4. 启动定时器
self.timer.start()
def update_count(self):
"""定时器触发的槽函数:更新计数"""
self.count += 1
self.label.setText(f"计数:{self.count}")
# 可选:计数到10停止定时器
if self.count >= 5:
self.timer.stop()
self.label.setText("计数结束!")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = TimerDemo()
window.show()
sys.exit(app.exec())

示例 2:单次触发定时器(延时执行)
单次触发可直接使用 QTimer.singleShot() 静态方法
import sys
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout
from PySide6.QtCore import QTimer
class SingleShotDemo(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.setWindowTitle("QTimer 单次触发示例")
self.btn = QPushButton("点击后3秒提示", self)
self.btn.clicked.connect(self.delayed_tip)
layout = QVBoxLayout()
layout.addWidget(self.btn)
self.setLayout(layout)
def delayed_tip(self):
"""点击按钮后,3秒后执行提示"""
self.btn.setEnabled(False)
# 静态方法:间隔(ms)、槽函数
QTimer.singleShot(3000, self.show_tip)
def show_tip(self):
self.btn.setText("3秒到了!")
self.btn.setEnabled(True)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SingleShotDemo()
window.show()
sys.exit(app.exec())

三、核心 API 与常用属性
1. 常用方法
| 方法 | 说明 |
|---|---|
QTimer(parent=None) |
构造函数,指定父对象可自动释放内存 |
setInterval(ms: int) |
设置定时间隔(毫秒),最小为 1ms |
interval() -> int |
获取当前间隔 |
start(ms: int = None) |
启动定时器;若传 ms,会覆盖之前的间隔 |
stop() |
停止定时器 |
isActive() -> bool |
判断定时器是否正在运行 |
setSingleShot(flag: bool) |
设置是否单次触发(True = 单次,False = 重复) |
isSingleShot() -> bool |
判断是否为单次触发 |
QTimer.singleShot(ms, slot) |
静态方法,快速创建单次定时器 |
2. 核心信号
| 信号 | 说明 |
|---|---|
timeout() |
定时器间隔到达时触发(核心信号) |
3. 静态常量(精度相关)
QTimer.PreciseTimer:高精度定时器(尽量保证间隔准确,适合对时间敏感的场景);QTimer.CoarseTimer:低精度定时器(间隔误差 ±5%,默认,性能更好);QTimer.VeryCoarseTimer:极低精度(间隔按秒对齐,适合无需精准的场景)。
设置精度示例:
self.timer.setTimerType(QtCore.Qt.PreciseTimer)
四、高级技巧
1. 动态调整定时器间隔
可在运行时修改间隔,无需停止定时器:
# 示例:点击按钮将间隔改为500ms
def change_interval(self):
self.timer.setInterval(500)
# 若定时器已启动,修改后立即生效
2. 多个定时器共享槽函数(区分来源)
通过 sender() 获取触发的定时器实例,实现多定时器统一处理:
import sys
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel
from PySide6.QtCore import QTimer
class MultiTimerDemo(QWidget):
def __init__(self):
super().__init__()
self.label1 = QLabel("定时器1:0")
self.label2 = QLabel("定时器2:0")
self.count1 = 0
self.count2 = 0
# 创建两个定时器
self.timer1 = QTimer(self)
self.timer1.setInterval(1000)
self.timer1.timeout.connect(self.update_timer)
self.timer2 = QTimer(self)
self.timer2.setInterval(2000)
self.timer2.timeout.connect(self.update_timer)
# 启动定时器
self.timer1.start()
self.timer2.start()
# 布局
layout = QVBoxLayout()
layout.addWidget(self.label1)
layout.addWidget(self.label2)
self.setLayout(layout)
self.setWindowTitle("多定时器共享槽函数")
def update_timer(self):
"""区分不同定时器更新计数"""
timer = self.sender() # 获取触发信号的定时器
if timer == self.timer1:
self.count1 += 1
self.label1.setText(f"定时器1:{self.count1}")
elif timer == self.timer2:
self.count2 += 1
self.label2.setText(f"定时器2:{self.count2}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MultiTimerDemo()
window.show()
sys.exit(app.exec())
3. 定时器与多线程(关键注意事项)
-
QTimer必须在创建它的线程中启动 / 停止,且该线程必须有事件循环(QThread.exec()); -
不能在非主线程直接更新 UI(Qt UI 组件仅支持主线程操作),需通过信号槽跨线程通信。
import sys
from PySide6.QtWidgets import QApplication, QWidget, QPushButton
from PySide6.QtCore import QTimer, QThread, Signal, QEventLoopclass WorkerThread(QThread):
progress = Signal(int)def __init__(self): super().__init__() self.count = 0 def run(self): # 在子线程里再建定时器,这样它才属于子线程 timer = QTimer() timer.setInterval(100) timer.timeout.connect(self.worker_task) timer.start() # 启动子线程事件循环 self.exec() # 用 self.exec() 而不是自建 QEventLoop def worker_task(self): self.count += 1 self.progress.emit(self.count) if self.count >= 20: self.quit() # 退出 exec() 事件循环class MainWindow(QWidget):
def init(self):
super().init()
self.btn = QPushButton("启动子线程定时器", self)
self.btn.clicked.connect(self.start_worker)
self.resize(300, 100)def start_worker(self): self.thread = WorkerThread() self.thread.progress.connect(self.update_ui) # 绑定信号到UI更新函数 self.thread.start() self.btn.setEnabled(False) def update_ui(self, count): """主线程更新UI""" self.btn.setText(f"子线程计数:{count}") if count >= 20: self.btn.setText("任务完成!") self.btn.setEnabled(True)if name == "main":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

五、常见问题与注意事项
1. 定时器不触发的原因
- 未启动事件循环(未调用
app.exec()); - 定时器实例被提前销毁(未设置父对象,且被 Python 垃圾回收);
- 定时器所在线程无事件循环(子线程需调用
QThread.exec()); - 间隔设置为 0 且非单次触发(会无限触发,可能阻塞事件循环)。
2. 避免定时器泄漏
- 给
QTimer设置父对象(如QTimer(self)),父对象销毁时自动释放; - 停止定时器后,断开信号槽连接(可选,父对象存在时无需)。
3. 高精度场景替代方案
若需微秒级精度,QTimer 无法满足,可使用:
QElapsedTimer:用于计算时间间隔(非定时器,仅计时);- 系统级定时器(如
time.perf_counter())+ 线程循环(需注意 CPU 占用)。
4. 避免定时器叠加触发
若槽函数执行时间超过定时器间隔,会导致 timeout 信号堆积,解决方案:
-
槽函数内先停止定时器,执行完再启动:
def update_count(self): self.timer.stop() # 耗时操作 self.count += 1 self.label.setText(f"计数:{self.count}") self.timer.start() -
增大定时器间隔,确保槽函数执行时间小于间隔。
六、总结
QTimer是 Qt 事件循环驱动的定时器,核心是timeout信号,支持单次 / 重复触发;- 主线程使用时可直接绑定 UI 操作,子线程使用需配合事件循环和信号槽;
- 优先使用
setSingleShot(True)或静态方法singleShot实现延时任务; - 注意线程安全,UI 操作必须在主线程执行,高精度场景需选择合适的定时器类型。
七、一个特殊的应用范例 QTimer.singleShot(0, self.func)
QTimer.singleShot(0, self.func) 是 PySide6 中一个非常特殊且高频使用 的写法,其核心含义是:让 self.func 跳过当前代码执行流程,优先进入 Qt 事件循环的下一轮执行(而非真正 "延时 0 毫秒")。
- 核心原理:为什么 "0 毫秒" 不代表立即执行?
Qt 的事件循环(QApplication.exec())是一个 "队列处理器",所有 UI 事件(点击、重绘)、信号槽、定时器事件都按队列顺序执行:
- 当你调用
QTimer.singleShot(0, func)时,Qt 会把func封装成一个 "定时事件",插入到事件队列的尾部; - 当前正在执行的代码(比如按钮点击的槽函数、初始化代码)会先完整执行完毕;
- 事件循环处理完当前队列中排在前面的事件后,才会执行这个 "0 毫秒" 的定时事件。
简单说:0毫秒 ≠ 立即执行,而是 "当前代码执行完后,下一个事件循环周期执行"。
- 关键使用场景(这才是它的核心价值)
这个写法看似简单,却是解决 Qt 界面开发中多个经典问题的 "神器",常见场景如下:
1. 避免 UI 阻塞 / 卡顿(让耗时操作 "延后" 执行)
如果在 UI 事件槽函数中执行耗时操作(比如循环计算、数据加载),会导致界面冻结(因为事件循环被阻塞,无法处理重绘、点击等事件)。用 0毫秒定时器 可以把耗时操作 "丢到" 当前事件处理完后执行,让 UI 先完成刷新。
示例:按钮点击后,由于执行了长耗时函数,按钮的文字并没有及时更新:
import sys
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel
from PySide6.QtCore import QTimer
class Demo(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("未执行", self)
self.btn = QPushButton("执行耗时操作", self)
self.btn.clicked.connect(self.on_click)
self.btn.move(0, 30)
self.resize(300, 100)
def on_click(self):
self.label.setText("开始执行...")
self.long_task()
def long_task(self):
# 模拟耗时操作
total = 0
for i in range(10_000_0000):
total += i
# 耗时操作完成后更新UI
self.label.setText(f"执行完成!结果:{total}")
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Demo()
w.show()
sys.exit(app.exec())

点击按钮后界面会卡住(标签不会立即显示 "开始执行");而用 0毫秒定时器 后,标签会先更新,耗时操作在后台执行,界面保持响应。
python
import sys
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel
from PySide6.QtCore import QTimer
class Demo(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("未执行", self)
self.btn = QPushButton("执行耗时操作", self)
self.btn.clicked.connect(self.on_click)
self.btn.move(0, 30)
self.resize(300, 100)
def on_click(self):
# 1. 先更新UI(让用户看到"开始执行"的反馈)
self.label.setText("开始执行...")
# 2. 把耗时操作丢到下一轮事件循环
QTimer.singleShot(0, self.long_task)
def long_task(self):
# 模拟耗时操作(比如循环1000万次)
total = 0
for i in range(10_000_0000):
total += i
# 耗时操作完成后更新UI
self.label.setText(f"执行完成!结果:{total}")
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Demo()
w.show()
sys.exit(app.exec())
2. 解决 "UI 初始化未完成时操作控件" 的问题
Qt 控件的初始化(比如 show() 之后)需要事件循环处理 "重绘事件" 才能完全就绪。如果在初始化代码中直接操作控件(比如设置焦点、滚动到指定位置),可能因为控件未就绪而失效。
示例:窗口显示后自动聚焦输入框
python
import sys
from PySide6.QtWidgets import QApplication, QWidget, QLineEdit, QVBoxLayout
from PySide6.QtCore import QTimer
class Demo(QWidget):
def __init__(self):
super().__init__()
self.input1 = QLineEdit(self)
self.input2 = QLineEdit(self)
# 错误写法:直接调用focus,控件未就绪,焦点可能不会生效
self.input2.setFocus()
layout = QVBoxLayout()
layout.addWidget(self.input1)
layout.addWidget(self.input2)
self.setLayout(layout)
self.resize(300, 100)
# 正确写法:0毫秒定时器,等窗口初始化完成后再聚焦
# QTimer.singleShot(0, self.input.setFocus)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Demo()
w.show()
sys.exit(app.exec())
3. 拆分长任务,让事件循环有机会处理其他事件
如果有一个超长的循环任务,即使放到 0毫秒定时器 中,仍会阻塞界面。可以用 0毫秒定时器 把长任务拆分成多个小任务,让事件循环有间隙处理 UI 事件。
示例:拆分长循环,避免界面冻结
python
运行
python
import sys
from PySide6.QtWidgets import QApplication, QWidget, QProgressBar, QVBoxLayout
from PySide6.QtCore import QTimer
class Demo(QWidget):
def __init__(self):
super().__init__()
self.progress = QProgressBar(self)
self.progress.setRange(0, 100)
layout = QVBoxLayout()
layout.addWidget(self.progress)
self.setLayout(layout)
self.resize(300, 100)
self.current = 0
# 启动第一个小任务
QTimer.singleShot(0, self.do_task_chunk)
def do_task_chunk(self):
# 每次只执行1%的任务(小chunk)
for _ in range(100000): # 小循环,快速完成
pass
self.current += 1
self.progress.setValue(self.current)
# 如果任务没完成,继续调度下一个小任务
if self.current < 100:
QTimer.singleShot(0, self.do_task_chunk)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Demo()
w.show()
sys.exit(app.exec())
4. 延迟信号槽的执行(解决信号触发时机问题)
某些场景下,信号会在控件状态未稳定时触发(比如 QComboBox.currentIndexChanged 在初始化时触发),用 0毫秒定时器 可以延后处理信号,确保拿到稳定的状态。
注意事项
- 并非真正的 "异步" :
0毫秒定时器仍在主线程 执行,如果func是极耗时操作,依然会阻塞界面(真正的异步需要用QThread); - 执行顺序 :
QTimer.singleShot(0, func)的任务会排在当前事件队列的尾部,比其他待处理的事件(比如鼠标点击、重绘)后执行; - 与
processEvents()的区别 :QCoreApplication.processEvents()是 "立即处理当前队列中的事件";QTimer.singleShot(0, func)是 "把 func 加入队列,等当前代码执行完后处理";
- 避免无限递归 :不要在
func中再次调用QTimer.singleShot(0, self.func)(除非有终止条件),否则会无限占用事件循环,导致界面无响应。
这是 Qt 开发中最实用的 "小技巧" 之一,尤其在 UI 初始化、事件处理、避免卡顿场景中高频使用。