PySide6雷达GUI运行中偶尔意外关闭问题求助
PySide6雷达GUI运行中偶尔意外关闭问题求助
兄弟,我看完你的代码后,立刻就发现了问题的核心——你违反了QT最基础的线程规则,这就是为啥程序偶尔会崩溃的原因!
先给你划重点:QT所有和GUI相关的操作(比如用QPainter绘图、修改QPixmap、更新Widget状态)必须在主线程(也就是QApplication所在的线程)执行,你现在在子线程item_placement_on_GUI里直接调用make_circle、make_box甚至radar这些操作GUI元素的方法,属于典型的线程不安全操作,这种行为的结果就是“大部分时候正常,偶尔突然崩溃”,完全符合你描述的现象。
下面给你具体的修复思路和代码优化方案:
一、核心问题拆解
- 子线程直接操作GUI元素:你的
item_placement_on_GUI线程直接调用绘图方法,QT的GUI对象不是线程安全的,跨线程操作会触发未定义行为; - 绘图逻辑冗余且不安全:你通过“先擦除再重绘剩余标记”的方式更新画面,不仅效率低,还容易因为线程同步问题导致数据混乱;
- 线程循环无休眠:无限循环不加
sleep会把CPU占满,也会加剧线程冲突的概率。
二、正确的解决方案:用信号槽机制实现线程安全的GUI更新
QT提供了信号-槽机制来解决跨线程GUI更新的问题:子线程只负责处理数据(比如管理标记的过期时间),然后通过发射信号把需要显示的数据传给主线程,由主线程来执行所有GUI操作。
三、优化后的代码示例
我给你修改了核心部分的代码,你可以直接参考替换:
import sys from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QPainter, QPixmap, QColor, QFont, QBrush from PySide6.QtWidgets import QApplication, QLabel, QMainWindow import threading import time import logging class MainWindow(QMainWindow): # 定义自定义信号:传递当前需要显示的所有标记列表 update_markers_signal = Signal(list) def __init__(self): super().__init__() self.label = QLabel() self.canvas = QPixmap(700, 700) self.label.setPixmap(self.canvas) self.setCentralWidget(self.label) self.radar() # 用一个列表存储所有标记:每个元素是(x, y, 过期时间, 颜色)的元组 self.markers = [] self.lock = threading.Lock() def radar(self): # 重绘雷达背景(和你原来的逻辑一致) self.canvas.fill(Qt.GlobalColor.white) painter = QPainter(self.canvas) painter.setPen(QColor(Qt.red)) painter.setFont(QFont('Arial', 20)) painter.drawText(348, 35, "0") painter.drawText(660, 350, "90") painter.drawText(325, 680, "180") painter.drawText(1, 350, "270") painter.setPen(QColor(Qt.black)) painter.drawEllipse(350, 350, 2, 2) painter.drawEllipse(200, 200, 300, 300) painter.drawEllipse(50, 50, 600, 600) painter.end() self.label.setPixmap(self.canvas) def draw_all_markers(self, markers): # 主线程的槽函数:负责绘制所有有效标记 self.radar() # 先重绘背景 painter = QPainter(self.canvas) for x, y, _, color in markers: brush = QBrush(color, Qt.BrushStyle.Dense1Pattern) painter.setBrush(brush) painter.drawEllipse(x, y, 10, 10) painter.end() self.label.setPixmap(self.canvas) def update_GUI(self, x:float, y:float, hz:int): # 添加新标记的逻辑:只处理数据,不碰GUI with self.lock: if hz == 260: color = QColor(0, 0, 255) elif hz == 440: color = QColor(255, 0, 0) expire_time = time.perf_counter() + 5 self.markers.append( (x, y, expire_time, color) ) def item_placement_on_GUI(self): # 子线程只负责数据维护和发射信号 while True: time.sleep(0.01) # 加休眠避免CPU占用过高 now = time.perf_counter() with self.lock: # 过滤掉过期的标记 self.markers = [m for m in self.markers if m[2] > now] # 拷贝当前有效标记,避免线程冲突 current_markers = self.markers.copy() # 发射信号给主线程更新GUI self.update_markers_signal.emit(current_markers) def test(): time.sleep(3) logging.info("Thread test is about to update 1") window.update_GUI(400, 250, hz=260) logging.info("Thread test is updated 1") time.sleep(3) logging.info("Thread test is about to update 2") window.update_GUI(500, 50, hz=260) logging.info("Thread test is updated 2") time.sleep(3) logging.info("Thread test is about to update 3") window.update_GUI(50, 300, hz=260) logging.info("Thread test is updated 3") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() # 连接信号和槽 window.update_markers_signal.connect(window.draw_all_markers) format = "%(asctime)s: %(message)s" logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S") x = threading.Thread(target=window.item_placement_on_GUI, daemon=True) x2 = threading.Thread(target=test, daemon=True) x.start() x2.start() window.show() app.exec()
四、关键优化点说明
- 信号槽机制:子线程只处理标记的过期过滤,通过
update_markers_signal把数据传给主线程,由主线程的draw_all_markers完成绘图,完全符合QT的线程规则; - 数据结构优化:用一个元组列表存储标记的所有信息,替代原来分散的x、y、timer列表,逻辑更清晰;
- 线程安全:用锁保护
markers列表的读写,避免多线程数据冲突; - 降低CPU占用:子线程循环里加了
time.sleep(0.01),不会把CPU跑满。
这样修改后,程序就不会再出现偶尔崩溃的问题了,而且代码结构更易维护。
备注:内容来源于stack exchange,提问作者simska
相关产品推荐
相关产品推荐

