PyQt5 GUI每秒更新多标签文本30次后冻结问题咨询
问题描述
我用PyQt5编写了一段代码,用于订阅一个30Hz(每秒发布30条消息)的ROS话题。当用户从下拉框选择gaze话题时,会触发create_subscriber_gaze_information函数创建ROS订阅者,其回调函数show_gaze_information会每秒30次更新GUI中的多个标签文本。但程序运行约10秒后,整个GUI会出现冻结现象。
相关代码
下拉框信号连接
# 当下拉框值变化时,创建gaze信息的ROS订阅者 ui.gaze_info_topic_name_value.activated.connect(create_subscriber_gaze_information)
创建订阅者函数
def create_subscriber_gaze_information(): # 取消订阅之前的话题 if ui.gaze_info_topic_name_value.ros_subs is not None: ui.gaze_info_topic_name_value.ros_subs.unregister() # 创建新订阅者 ui.gaze_info_topic_name_value.ros_subs = rospy.Subscriber( name=ui.gaze_info_topic_name_value.currentText(), data_class=gazesense_msgs.msg.TrackedUserArray, callback=show_gaze_information, queue_size=1)
更新标签回调函数
def show_gaze_information(gaze_msg): if ui.groupBox_gaze.is_lock == True: return ui.groupBox_gaze.is_lock = True for person in gaze_msg.people: ################################# # 头部信息 ################################# # 设置头部平移X值 ui.label_head_translation_x_value.setText("{}".format(person.tracking_info.head_pose.transform.translation.x)) # 设置头部平移Y值 ui.label_head_translation_y_value.setText("{}".format(person.tracking_info.head_pose.transform.translation.y)) # 设置头部平移Z值 ui.label_head_translation_z_value.setText("{}".format(person.tracking_info.head_pose.transform.translation.z)) # 设置头部旋转X值 ui.label_head_rotation_x_value.setText("{}".format(person.tracking_info.head_pose.transform.rotation.x)) # 设置头部旋转Y值 ui.label_head_rotation_y_value.setText("{}".format(person.tracking_info.head_pose.transform.rotation.y)) # 设置头部旋转Z值 ui.label_head_rotation_z_value.setText("{}".format(person.tracking_info.head_pose.transform.rotation.z)) # 设置头部旋转W值 ui.label_head_rotation_w_value.setText("{}".format(person.tracking_info.head_pose.transform.rotation.w)) # 设置是否丢失跟踪 ui.label_head_is_lost_value.setText("{}".format(person.tracking_info.head_pose.is_lost)) ################################# # 左眼信息 ################################# # 设置左眼原点X值 ui.label_left_eye_orgin_x_value.setText("{}".format(person.tracking_info.gaze.left_eye_ray.origin.x)) # 设置左眼原点Y值 ui.label_left_eye_orgin_y_value.setText("{}".format(person.tracking_info.gaze.left_eye_ray.origin.y)) # 设置左眼原点Z值 ui.label_left_eye_orgin_z_value.setText("{}".format(person.tracking_info.gaze.left_eye_ray.origin.z)) # 设置左眼方向X值 ui.label_left_eye_dir_x_value.setText("{}".format(person.tracking_info.gaze.left_eye_ray.direction.x)) # 设置左眼方向Y值 ui.label_left_eye_dir_y_value.setText("{}".format(person.tracking_info.gaze.left_eye_ray.direction.y)) # 设置左眼方向Z值 ui.label_left_eye_dir_z_value.setText("{}".format(person.tracking_info.gaze.left_eye_ray.direction.z)) # 设置左眼置信度 ui.label_left_eye_conf_value.setText("{}".format(person.tracking_info.gaze.confidence_left.value)) # 设置左眼是否丢失跟踪 ui.label_left_eye_is_lost_value.setText("{}".format(person.tracking_info.gaze.is_lost)) ui.groupBox_gaze.is_lock = False
问题原因
ROS订阅者的回调函数运行在ROS后台线程中,而PyQt的GUI操作必须在**主线程(UI线程)**中执行。直接在回调里更新UI组件会引发线程冲突,大量跨线程的UI更新操作会堵塞事件队列,最终导致GUI冻结。
另外,30Hz的更新频率本身较高,即使加了锁,也只能防止同一回调的并发执行,无法解决跨线程操作UI的核心问题。
解决方案
使用PyQt的pyqtSignal实现线程间通信,将ROS回调中获取的数据发送到主线程,由主线程安全更新UI:
步骤1:定义信号类(或在主窗口类中添加信号)
from PyQt5.QtCore import pyqtSignal, QObject # 定义信号类用于传递ROS数据 class RosDataSignals(QObject): gaze_data_updated = pyqtSignal(object) # 传递解析后的人员数据列表 # 在主窗口初始化时创建信号实例并连接到UI更新函数 self.ros_signals = RosDataSignals() self.ros_signals.gaze_data_updated.connect(self.update_gaze_ui)
步骤2:修改ROS回调函数,仅处理数据并发送信号
def show_gaze_information(gaze_msg): # 只传递数据,不操作UI self.ros_signals.gaze_data_updated.emit(gaze_msg.people)
步骤3:在主线程实现UI更新函数
该函数运行在主线程,可安全更新UI组件:
def update_gaze_ui(people_list): if ui.groupBox_gaze.is_lock: return ui.groupBox_gaze.is_lock = True for person in people_list: # 更新头部信息 ui.label_head_translation_x_value.setText(f"{person.tracking_info.head_pose.transform.translation.x}") ui.label_head_translation_y_value.setText(f"{person.tracking_info.head_pose.transform.translation.y}") ui.label_head_translation_z_value.setText(f"{person.tracking_info.head_pose.transform.translation.z}") ui.label_head_rotation_x_value.setText(f"{person.tracking_info.head_pose.transform.rotation.x}") ui.label_head_rotation_y_value.setText(f"{person.tracking_info.head_pose.transform.rotation.y}") ui.label_head_rotation_z_value.setText(f"{person.tracking_info.head_pose.transform.rotation.z}") ui.label_head_rotation_w_value.setText(f"{person.tracking_info.head_pose.transform.rotation.w}") ui.label_head_is_lost_value.setText(f"{person.tracking_info.head_pose.is_lost}") # 更新左眼信息 ui.label_left_eye_orgin_x_value.setText(f"{person.tracking_info.gaze.left_eye_ray.origin.x}") ui.label_left_eye_orgin_y_value.setText(f"{person.tracking_info.gaze.left_eye_ray.origin.y}") ui.label_left_eye_orgin_z_value.setText(f"{person.tracking_info.gaze.left_eye_ray.origin.z}") ui.label_left_eye_dir_x_value.setText(f"{person.tracking_info.gaze.left_eye_ray.direction.x}") ui.label_left_eye_dir_y_value.setText(f"{person.tracking_info.gaze.left_eye_ray.direction.y}") ui.label_left_eye_dir_z_value.setText(f"{person.tracking_info.gaze.left_eye_ray.direction.z}") ui.label_left_eye_conf_value.setText(f"{person.tracking_info.gaze.confidence_left.value}") ui.label_left_eye_is_lost_value.setText(f"{person.tracking_info.gaze.is_lost}") ui.groupBox_gaze.is_lock = False
额外优化建议
- 调整ROS订阅者的
queue_size:当前设置为1,30Hz的话题可适当调大(比如5),减少消息丢失同时降低回调触发压力。 - 降低UI更新频率:若无需30Hz的更新速度,可在主线程设置定时器(如每30ms更新一次),仅取最新消息数据,减少UI刷新次数。
内容的提问来源于stack exchange,提问作者CVDE
相关产品推荐
相关产品推荐

