Publish by2025-07-07
Updated by2025-07-11
近两月和这个杆上了,估摸 Q3 还得和它 Battle N 回。
Written by - kok-s0s
Qt 中滥用
QCoreApplication::processEvents()引发崩溃
QCoreApplication::processEvents() 作用是:立即处理当前事件队列中的所有待处理事件(如 GUI 更新、信号槽调用、定时器事件等),避免界面假死。
在一个路径校验函数中,为了避免 UI 卡顿,在主线程里,这个路径校验循环中调用了:
QCoreApplication::processEvents(); // 处理事件,避免界面假死
初衷很简单:希望用户在等待过程(校验同步处理且耗时)中界面不要“卡死”,能显示日志、响应按钮等操作。
但实际运行中,程序出现了 随机崩溃,并且崩溃点并不在 processEvents() 行,而是类似下面的调用栈:
[E|07/02 16:55:41.072]Signal[6] arrive, will shutdown system and dump stack info
[E|07/02 16:55:41.073]Write codedump failed, reason:-1
[E|07/02 16:55:41.092]-------------------Back Trace Info-------------------
[E|07/02 16:55:41.093]1 0x7f819d37d0 __kernel_rt_sigreturn + 0
[E|07/02 16:55:41.093]2 0x7f7fe46d78 gsignal + 224
[E|07/02 16:55:41.093]3 0x7f7fe33aac abort + 276
[E|07/02 16:55:41.094]4 0x7f7fe80f40 /lib/aarch64-linux-gnu/libc.so.6(+0x6df40) [0x7f7fe80f40]
[E|07/02 16:55:41.094]5 0x7f7fe88344 /lib/aarch64-linux-gnu/libc.so.6(+0x75344) [0x7f7fe88344]
[E|07/02 16:55:41.094]6 0x7f7fe89c90 /lib/aarch64-linux-gnu/libc.so.6(+0x76c90) [0x7f7fe89c90]
[E|07/02 16:55:41.095]7 0x7f81328490 /lib/aarch64-linux-gnu/libQt5Widgets.so.5(+0x3bf490) [0x7f81328490]
[E|07/02 16:55:41.095]8 0x7f8132972c QAbstractItemView::dataChanged(QModelIndex const&, QModelIndex const&, QVector<int> const&) + 340
[E|07/02 16:55:41.096]9 0x7f8132bc2c /lib/aarch64-linux-gnu/libQt5Widgets.so.5(+0x3c2c2c) [0x7f8132bc2c]
[E|07/02 16:55:41.096]10 0x7f80563608 QObject::event(QEvent*) + 512
[E|07/02 16:55:41.097]11 0x7f81109420 QWidget::event(QEvent*) + 568
[E|07/02 16:55:41.097]12 0x7f811a87b4 QFrame::event(QEvent*) + 44
[E|07/02 16:55:41.098]13 0x7f811ab404 QAbstractScrollArea::event(QEvent*) + 588
[E|07/02 16:55:41.098]14 0x7f8132f89c QAbstractItemView::event(QEvent*) + 308
[E|07/02 16:55:41.099]15 0x7f810c74ac QApplicationPrivate::notify_helper(QObject*, QEvent*) + 92
[E|07/02 16:55:41.099]16 0x7f810d0ad8 QApplication::notify(QObject*, QEvent*) + 336
[E|07/02 16:55:41.100]17 0x7f80535c0c QCoreApplication::notifyInternal2(QObject*, QEvent*) + 380
[E|07/02 16:55:41.100]18 0x7f80538b80 QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) + 360
[E|07/02 16:55:41.101]19 0x7f80591d20 /lib/aarch64-linux-gnu/libQt5Core.so.5(+0x2c7d20) [0x7f80591d20]
[E|07/02 16:55:41.101]20 0x7f7f53494c g_main_context_dispatch + 636
[E|07/02 16:55:41.101]21 0x7f7f534bbc /lib/aarch64-linux-gnu/libglib-2.0.so.0(+0x51bbc) [0x7f7f534bbc]
[E|07/02 16:55:41.102]22 0x7f7f534c5c g_main_context_iteration + 52
[E|07/02 16:55:41.102]23 0x7f805911cc QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 84
这类栈结构非常隐晦,让人误以为是 UI 或模型的 bug。深入排查之后才发现,根因竟然就是这一行 processEvents() 的调用!
路径校验函数大致如下:
bool ProgrammingPage::checkPointPath() {
...
foreach (auto point, m_teaching_points) {
...
QCoreApplication::processEvents(); // 防止UI卡顿
}
...
}
当某些 UI 或模型(如 QTableView, QStandardItemModel)在这期间恰好被修改或关闭时,Qt 主事件循环会尝试发出如 dataChanged() 等信号,这些信号若对应的控件或模型已销毁,就会导致 Qt 崩溃。
经典的 “事件重入 + 对象提前销毁” 问题!
调用 QCoreApplication::processEvents() 相当于强制 Qt 提前处理一轮主事件循环,但:
主线程正在执行路径校验逻辑
而主事件循环中可能还有:
一旦触发类似 QAbstractItemView::dataChanged(),但视图或模型已无效,就会出现 不可控的访问空指针或野指针,导致程序直接挂掉。
❶ 避免使用 processEvents() ——特别是在循环或逻辑中!
如果只是为了不卡 UI,推荐改为异步调度:
// 使用 QTimer 逐个检查点位
QTimer::singleShot(0, this, &ProgrammingPage::checkNextPoint);
❷ 需要处理事件时,限定处理时间
QEventLoop loop;
QTimer::singleShot(10, &loop, &QEventLoop::quit); // 只处理10ms事件
loop.exec();
这样就不会无限执行主事件循环,避免过早触发 UI 事件。
❸ 任何调用 UI 的地方都要确保:
Qt::QueuedConnection 避免重入| 错误操作 | 后果 |
|---|---|
在主线程中直接调用 QCoreApplication::processEvents() | Qt 主事件循环乱入,可能在你意想不到的时候发出信号、修改模型、销毁 UI,最终导致崩溃 |
| 没有检查模型、UI 控件是否有效 | 引发 QAbstractItemView::dataChanged() 崩溃等问题 |
| 没有使用异步任务拆分耗时处理 | 线程卡顿 / UI 卡死 / 崩溃 |
❗
processEvents()并不是“万能润滑剂”,它更像是一个危险的开关:开得好,程序不卡;开不好,程序直接炸。
| 场景 | 推荐方式 |
|---|---|
| 防止 UI 卡顿 | 用 QTimer 拆分任务 |
| 任务耗时长 | 搬到 QThread 线程处理,UI 主线程只更新界面 |
| 必须等待但不想卡住 UI | QEventLoop + QTimer::singleShot() 控制处理时间 |
部分指针的初始化依赖于配置文件或外部条件,一旦初始化失败或返回空值,而后续代码未进行空指针检查便直接访问该指针,极易触发崩溃。
[E|06/25 22:02:30.710]1 0x7f7a10e6c0 __kernel_rt_sigreturn + 0
[E|06/25 22:02:30.710]2 0x7f76cc94d8 raise + 176
nullptr 检查QSharedPointer)或 Qt 的 QPointer 进行管理,增强鲁棒性QObject 连接信号槽后,如果被连接对象被销毁但信号仍然触发,就会访问到已释放的内存,导致程序崩溃。
connect(button, &QPushButton::clicked, this, &MyWidget::onClicked);
// 若 this 提前 delete,但 signal 仍触发,访问非法
delete,信号触发时仍调用旧槽函数QObject::deleteLater() 延迟销毁,确保事件处理完再释放QPointer 检查槽接收者是否已释放AutoConnection 会在多线程中自动使用队列连接,增强安全性Qt 中 GUI 控件必须运行在主线程,如果从工作线程访问 UI,会立即引发未定义行为甚至崩溃。
void* run() {
ui->label->setText("thread text"); // 非法访问 GUI 控件
}
moveToThread() 移交线程归属权Qt::QueuedConnection)通知 UI 更新moveToThread() 完整迁移其线程归属未调用 setupUi() 或控件未成功添加到布局中,或通过 findChild() 获取控件失败返回 nullptr,但后续仍继续访问,导致程序崩溃。
.ui 文件未正确加载findChild() 查找失败if (!ptr))setupUi(this) 初始化界面Q_ASSERT 在调试模式中验证控件有效性Qt 默认不启用 C++ 异常处理机制(尤其在 GUI 主线程),一旦抛出异常但未被捕获,将导致程序直接中止。
throw 抛出int main(int argc, char *argv[]) {
try {
QApplication app(argc, argv);
MainWindow w;
w.show();
return app.exec();
} catch (std::exception &e) {
qCritical() << "Unhandled exception:" << e.what();
return -1;
}
}
在使用定时器或线程时,如果其父对象未绑定或生命周期不一致,极易导致对象提前销毁,后续触发事件访问到悬空对象而崩溃。
QObject 派生类时,始终指定 parenttimer->moveToThread(workerThread);
QThread::quit() + wait() 安全退出线程deleteLater() 而非直接 delete