Qt 5.15.2中QtConcurrent::mapped致界面阻塞的原因及解决办法
相关代码
main.cpp
#include <QApplication> #include <QtConcurrent/QtConcurrent> int numTest(const int &num) { QThread::msleep(5000); qDebug() << "numTest in thread" << QThread::currentThread(); return num; } int main(int argc, char *argv[]) { QApplication app(argc, argv); QtConcurrent::run([](){ const int count = 2000; QList<int> nums; for (int i = 0; i < count; ++i) nums.append(i); auto result = QtConcurrent::mapped(nums, numTest); result.waitForFinished(); }); BmMessageBox::information(nullptr, "tips", "There is no help document temporarily!"); return app.exec(); }
bmmessagebox.h
#ifndef BMMESSAGEBOX_H #define BMMESSAGEBOX_H #include <QDialog> #include <QIcon> class BmMessageBox : QObject { Q_OBJECT public: static void information(QWidget *parent, const QString &title, const QString &text) { Q_UNUSED(title); Q_UNUSED(text); QDialog box(parent); box.setWindowIcon(QIcon(":/resources/images/test.png")); box.exec(); } }; #endif // BMMESSAGEBOX_H
resources.qrc
<RCC> <qresource prefix="/"> <file>resources/images/test.png</file> </qresource> </RCC>
问题描述
在Qt 5.15.2版本中,使用QtConcurrent::mapped时,调用BmMessageBox::information设置资源文件中的窗口图标会导致界面阻塞,但该问题在Qt 5.12.12版本中并未出现。请问该问题的成因是什么?如何在不更换Qt版本的前提下解决此问题?
问题成因
Qt 5.15.x对QtConcurrent线程池和资源文件访问的线程安全性做了调整:
QtConcurrent::mapped会启动大量工作线程,而Qt 5.15.2中QResource(资源文件)的访问新增了全局锁机制,当工作线程和主线程同时尝试访问资源时,会出现锁竞争。- 主线程显示模态对话框
box.exec()时会占用事件循环,同时QtConcurrent::run里的result.waitForFinished()会让启动任务的线程一直等待,双重叠加导致主线程被资源锁阻塞,最终界面卡死。
解决方案
方案1:提前预加载资源
在主线程启动QtConcurrent任务前,先加载一次需要的资源,将其缓存到内存中,避免后续模态对话框加载时触发锁竞争:
int main(int argc, char *argv[]) { QApplication app(argc, argv); // 预加载资源到内存 QIcon preloadIcon(":/resources/images/test.png"); QtConcurrent::run([](){ const int count = 2000; QList<int> nums; for (int i = 0; i < count; ++i) nums.append(i); auto result = QtConcurrent::mapped(nums, numTest); result.waitForFinished(); }); BmMessageBox::information(nullptr, "tips", "There is no help document temporarily!"); return app.exec(); }
方案2:避免阻塞QtConcurrent启动线程
去掉result.waitForFinished(),让任务在后台异步执行,不占用启动线程的资源,减少锁竞争概率:
int main(int argc, char *argv[]) { QApplication app(argc, argv); QtConcurrent::run([](){ const int count = 2000; QList<int> nums; for (int i = 0; i < count; ++i) nums.append(i); // 直接启动任务,不等待完成 QtConcurrent::mapped(nums, numTest); }); BmMessageBox::information(nullptr, "tips", "There is no help document temporarily!"); return app.exec(); }
如果需要监听任务完成状态,可使用QFutureWatcher绑定信号:
int main(int argc, char *argv[]) { QApplication app(argc, argv); QFutureWatcher<int> watcher; QObject::connect(&watcher, &QFutureWatcher<int>::finished, [](){ qDebug() << "所有任务执行完成"; }); QtConcurrent::run([&watcher](){ const int count = 2000; QList<int> nums; for (int i = 0; i < count; ++i) nums.append(i); auto result = QtConcurrent::mapped(nums, numTest); watcher.setFuture(result); }); BmMessageBox::information(nullptr, "tips", "There is no help document temporarily!"); return app.exec(); }
方案3:绕过资源文件锁
将图标文件作为独立本地文件存放,而非嵌入到资源中,直接访问本地文件避免QResource的全局锁:
// 修改BmMessageBox::information中的图标加载代码 box.setWindowIcon(QIcon("resources/images/test.png"));
需确保程序运行时该路径下存在对应的图片文件。
内容的提问来源于stack exchange,提问作者bmseven
相关产品推荐
相关产品推荐

