一次Qt上位机图表不绘制问题的完整调试与重构复盘

接手大型Qt项目,发现封装后的图表无法绘制,经过长达数天的追踪,最终定位到信号槽连接、对象生命周期、多线程和性能优化等多个层面的复合问题。本文记录完整的排查思路与解决方案。


一、背景

我接手了一个Qt上位机项目,该项目用于控制多台“手”(设备)的数据采集与图表显示。原有代码有061、062、063、064、066共5个设备,每个设备都有独立的图表初始化函数,存在大量重复代码。

为了提升可维护性,我进行了一次重构:

  • 将061、062、063、066的图表初始化统一到 chartInit() 方法,通过传入自定义结构体 ChartConfig 和两个控件参数来区分。

  • 将多个独立的 showDataOnChart_06x 信号合并为一个 showDataOnChart 信号。

  • 优化了内存管理,给所有 new 的对象指定了父对象(this)。

  • 调整了线程启动顺序,确保 moveToThread 先于 start

重构完成后,发现 063号手 的图表无法绘制曲线,而 064号手 依然正常工作。经过多轮排查,最终解决了问题。本文将复盘整个调试过程,记录思考路径与最终方案。


二、问题现象

  • 点击063的“开始”按钮,flag_record 被置为1,但图表的 Update_data_Slot 槽函数中 if(flag_record == 1) 判断始终不进入。

  • 在槽函数中打断点,发现断点总是停在064的对象地址上,063的槽函数一次都没有被执行。

  • 恢复封装前的旧代码后,063图表虽然能偶尔绘制,但延迟非常严重(长达数秒)。

  • 064一直工作正常,无延迟。


三、调试过程(逐步逼近真相)

第1步:确认 flag_record 是否被正确设置

我在设置 flag_record = 1 的地方打印 this 指针地址,发现该地址为 0x3f418300(063对象)。而在 Update_data_Slot 的断点处,打印 this 地址却是 0x438ff6b0(064对象)。这验证了 设置和判断不在同一个对象上

第2步:检查信号槽连接

  • 使用 qDebug() 输出 connect 返回值,均为 true,说明连接语法和参数匹配无误。

  • 检查线程亲和性:信号发送者、064对象、063对象都在主线程,排除线程问题。

  • 确认没有多余的 disconnect 或重复 connect 覆盖。

第3步:排查对象生命周期

  • 在构造函数中,chartInit 被调用4次(分别初始化061、062、063、066)。每次调用都会 new CChart_Ins 并将指针赋给对应的 config_xx.chartLinetype

  • 但是,connect 语句只写在构造函数末尾一次,且绑定的是 config_063.chartLinetype

  • 由于 chartInit 的执行顺序,config_063.chartLinetype 可能在 connect 执行时还未 new(或刚刚被覆盖),导致连接绑定到了一个无效或临时的对象。

  • 进一步验证:在 connect 前后分别打印 config_063.chartLinetype 地址,发现执行 connect 时该指针为 nullptr,后续虽然 new 了对象,但连接已丢失。

结论:封装后 new 和 connect 分离,导致连接时机错误。

第4步:尝试“集中分发”方案

为了彻底摆脱信号槽连接的时序问题,我改用 “信号集中分发 + 直接调用” 模式:

  • 在 MainWindow 中只连接一次信号到 onDataArrived 槽。

  • 在 onDataArrived 中,手动调用每个图表对象的 Update_data_Slot 函数(直接函数调用,不经信号槽)。

这样做之后,063的断点终于触发了,图表开始绘制,但出现了 严重延迟(数秒后才画出曲线)。

第5步:分析延迟原因

在 Update_data_Slot 首尾加时间戳打印,发现单次绘制耗时极短(<1ms)。但为何界面延迟?

观察 onDataArrived 的实现,我原本是 无条件调用所有图表对象的 Update_data_Slot,即同时调用了061、062、063、064、066五个对象的绘图函数。虽然每个函数耗时很短,但 五个对象同时重绘,导致主线程事件循环被大量 update() 请求淹没,界面刷新被排队延迟。

而 064 之前正常,是因为它独占信号,只有一个对象在绘制,CPU 负载低,没有延迟。

第6步:加入模式过滤,彻底解决

项目中有 Device_Mode 变量标识当前激活的设备。我在 onDataArrived 中添加了条件判断:

if (currentMode == MODE_064 && chart_Line64) {
    chart_Line64->Update_data_Slot(time, data);
} else if (currentMode == MODE_063 && config_063.chartLinetype) {
    config_063.chartLinetype->Update_data_Slot(time, data);
}

这样每次数据到达时,只更新当前激活的那一个图表,CPU 负载恢复正常,063 图表瞬间绘制,无延迟。


四、根本原因总结

本次 Bug 是由 三个层次的问题叠加 引起的:

  1. 信号槽连接与对象生命周期不同步

    • 重构后,new 操作放在 chartInit 中(被调用4次),但 connect 只执行了一次,且时机不对,导致 063 的对象未正确绑定信号。

  2. 多对象同时接收信号导致主线程拥堵

    • 集中分发时,所有图表对象同时绘制,造成 UI 线程过载,出现延迟假象。

  3. 缺少模式过滤

    • 分发逻辑未区分当前激活设备,导致无用绘制消耗 CPU 资源。


五、最终解决方案

5.1 统一信号

将多个独立的信号合并为一个 showDataOnChart,在发射处根据 Device_Mode 统一 emit

5.2 集中分发

在 MainWindow 中只建立一条连接:

connect(m_inspire, &InspireHand::showDataOnChart,
        this, &MainWindow::onDataArrived);

5.3 实现带模式过滤的分发槽

void MainWindow::onDataArrived(qint64 time, QList<double> data)
{
    if (currentMode == MODE_064 && chart_Line64) {
        chart_Line64->Update_data_Slot(time, data);
    } else if (currentMode == MODE_063 && config_063.chartLinetype) {
        config_063.chartLinetype->Update_data_Slot(time, data);
    }
    // 其他模式...
}

5.4 删除其他所有 connect 语句

确保只有上述一条连接,避免干扰。


六、经验教训

关键点教训
new 与 connect 必须配对尽量将 connect 紧跟 new 之后,或在同一函数内完成,避免时序错乱。
重构时注意“一对多”场景合并多个初始化函数时,同步更新所有依赖关系(如 connect)。
多对象共享信号时,务必做模式过滤不要让所有对象都在后台做无用功,根据业务逻辑只让当前活跃的对象处理数据。
调试“延迟”时,优先检查 CPU 负载延迟不一定是信号没连上,也可能是连接后处理任务过重。
善用 this 指针打印打印 this 地址是定位“对象错乱”的最直接手段。
connect 返回 true 不代表最终有效若接收者指针在连接后被覆盖,连接会失效,但返回值仍为 true

七、结语

这次调试经历让我深刻体会到:大型项目的重构,不仅要考虑代码复用,更要关注对象生命周期、信号槽绑定时机、以及多对象并发时的性能影响。

最终方案既保留了重构的优雅性(统一信号、集中分发),又通过模式过滤保证了性能,是一次成功的架构优化。

如果你也遇到类似问题,希望这篇博客能给你一些启发。欢迎留言交流!


附:关键代码片段

// MainWindow.h
private slots:
    void onDataArrived(qint64 time, QList<double> data);

// MainWindow.cpp 构造函数
connect(m_inspire, &InspireHand::showDataOnChart,
        this, &MainWindow::onDataArrived);

// 实现
void MainWindow::onDataArrived(qint64 time, QList<double> data)
{
    if (currentMode == MODE_064 && chart_Line64) {
        chart_Line64->Update_data_Slot(time, data);
    } else if (currentMode == MODE_063 && config_063.chartLinetype) {
        config_063.chartLinetype->Update_data_Slot(time, data);
    }
    // 其他模式...
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值