QtConcurrent::run操作界面ui的注意事项(1)

先说结论:QtConcurrent::run启动的耗时处理函数,不允许处理ui界面对象,如控件,如进度条等等!

QtConcurrent::run非常好用,胜过QThead的两种方式(run和moveToThread),例如下面是非常直观和简单的使用方式:

QT += concurrent

#include <QtConcurrent>
#include <QThread>


void MainWindow::doSomeWork()//死循环操作,代替原有代码的函数功能
{
   int i=0;
    while(myEnable)
    {
        i++;
        qDebug()<<i;
		QThread::msleep(10);
    }
}

void MainWindow::myStart()//启动函数是需要在原有代码基础上增加
{
	myEnable=1;
    QtConcurrent::run(this,&MainWindow::doSomeWork);//多线程执行死循环启动,可以带参数

void MainWindow::myExit()
{
    myEnable=0;//
}

但是,但是,上述的MainWindow::doSomeWork函数中不能访问ui对象,会引起程序崩溃!!原因后面解释。先说解决办法:

一种方法是,MainWindow::doSomeWork能读写简单类型的数值变量,int,float类型,则可以借助这些简单类型变量做个过渡中转一下。既然是ui界面对象,是给人看的,延时几十个ms,人的眼睛不会有感觉,因此,开一个定时器,轮询这些简单的数值int、float变量,在定时器里去更新ui界面对象,这是最简单的方法;

另一种方法是,在调用QtConcurrent::run(this,&MainWindow::doSomeWork)的地方,检查返回值,开一个循环等待完成。在循环里完成对界面ui的操作,如下面在按钮点击事件中:


                
void ImageItem::setFilename(QString filename) { // QDateTime beginTime = QDateTime::currentDateTime(); m_pdf->loadFile(filename); m_bookmarkModel->clear(); // m_pdf->bookmark().debugMesg(); getBookmarkList(m_pdf->bookmark(), m_bookmarkModel->invisibleRootItem()); for (int i = 0/*start*/; i <= /*end*/m_pdf->pageCount(); i++) { auto page = m_pdf->page(i); if (!page.isValid()) { qDebug() << "Invalid page at index:" << i; m_pageImages.append(QImage()); // 存入空图片 continue; } QImage image = page.image(2); // 渲染页面 if (image.isNull()) { qDebug() << "Failed to render page:" << i; m_pageImages.append(QImage()); } else { m_pageImages.append(image); } } Q_EMIT readyChanged(m_pdf->isValid()); }实现多线程获取到m_pageImages 你可以继续问我: 使用QtConcurrent的多线程特性有哪些缺点 QtConcurrent.run() 是如何使用 的 Qt的线程模型是什么 是的,QtConcurrentQt 提供的多线程并发框架,它基于 QThreadPool 和 Qt 的线程模型,能够自动在后台线程中执行任务,而无需开发者手动管理线程生命周期。 QtConcurrent 的多线程特性 自动线程管理 QtConcurrent::run() 会将任务提交到 全局线程池(QThreadPool),由 Qt 自动分配线程执行。 开发者无需手动创建 QThread、moveToThread() 或管理线程生命周期。 基于任务(Task-based) 适用于计算密集型任务(如 PDF 页面渲染、图像处理、数据计算等)。 不适用于需要长期运行或频繁交互的任务(如网络请求、事件监听等,这些更适合 QThread 或 QRunnable)。 隐式并行 QtConcurrent::mapped()QtConcurrent::filtered() 等函数会自动并行化操作,利用多核 CPU 加速处理。 线程安全 如果任务涉及共享数据,必须使用互斥锁(QMutex)、信号槽(Qt::QueuedConnection) 或 线程安全的数据结构(如 QReadWriteLock)来保护数据。 QtConcurrent vs. QThread / QRunnable 特性 QtConcurrent QThread QRunnable 线程管理 自动(基于 QThreadPool) 手动(需继承 QThread 或 moveToThread) 手动(需提交到 QThreadPool) 适用场景 计算密集型任务(如并行处理) 需要精细控制线程(如事件循环) 轻量级任务(如短时间运行的任务) 并行能力 自动并行(如 mapped()、filtered()) 需手动实现并行逻辑 需手动实现并行逻辑 代码复杂度 简单(函数式编程风格) 较高(需管理线程生命周期) 中等(需实现 run() 方法) QtConcurrent 在 PDF 渲染中的应用 1. 基本用法(单任务) cpp #include <QtConcurrent/QtConcurrent> void ImageItem::setFilename(QString filename) { m_pdf->loadFile(filename); m_bookmarkModel->clear(); getBookmarkList(m_pdf->bookmark(), m_bookmarkModel->invisibleRootItem()); int pageCount = m_pdf->pageCount(); m_pageImages.resize(pageCount); // 使用 QtConcurrent::run 在后台线程渲染页面 QFuture<void> future = QtConcurrent::run([this, pageCount]() { for (int i = 0; i < pageCount; ++i) { auto page = m_pdf->page(i); QImage image; if (!page.isValid()) { qDebug() << "Invalid page at index:" << i; image = QImage(); } else { image = page.image(2); // 渲染页面 if (image.isNull()) { qDebug() << "Failed to render page:" << i; image = QImage(); } } // 使用 QMetaObject::invokeMethod 更新主线程数据 QMetaObject::invokeMethod(this, [this, i, image]() { if (i < m_pageImages.size()) { m_pageImages[i] = image; } }, Qt::QueuedConnection); } }); // 可选:等待所有任务完成(阻塞主线程) // future.waitForFinished(); } 问题: 这种方式是顺序执行(虽然在一个后台线程),没有利用多核并行。 频繁调用 QMetaObject::invokeMethod 会影响性能。 2. 并行渲染(使用 QtConcurrent::mapped) cpp #include <QtConcurrent/QtConcurrent> #include <QFutureWatcher> void ImageItem::setFilename(QString filename) { m_pdf->loadFile(filename); m_bookmarkModel->clear(); getBookmarkList(m_pdf->bookmark(), m_bookmarkModel->invisibleRootItem()); int pageCount = m_pdf->pageCount(); m_pageImages.resize(pageCount); // 创建 QFutureWatcher 监控任务进度 QFutureWatcher<void>* watcher = new QFutureWatcher<void>(this); connect(watcher, &QFutureWatcher<void>::finished, [this, watcher]() { watcher->deleteLater(); Q_EMIT readyChanged(m_pdf->isValid()); }); // 使用 QtConcurrent::mapped 并行渲染所有页面 QFuture<void> future = QtConcurrent::run([this, pageCount]() { QVector<QImage> tempImages(pageCount); // 临时存储渲染结果 // 并行渲染每个页面 QtConcurrent::blockingMap(tempImages, [this](QImage& image, int index) { auto page = m_pdf->page(index); if (!page.isValid()) { qDebug() << "Invalid page at index:" << index; image = QImage(); return; } image = page.image(2); if (image.isNull()) { qDebug() << "Failed to render page:" << index; image = QImage(); } }); // 使用 QMetaObject::invokeMethod 更新主线程数据 QMetaObject::invokeMethod(this, [this, tempImages]() { m_pageImages = tempImages; // 原子替换 }, Qt::QueuedConnection); }); watcher->setFuture(future); } 优化点: QtConcurrent::mapped 会自动并行化渲染任务,利用多核 CPU 加速。 QFutureWatcher 用于监控任务完成状态,避免阻塞主线程。 QMetaObject::invokeMethod 确保线程安全地更新 m_pageImages。 总结 QtConcurrent 是多线程的,基于 QThreadPool 自动管理线程。 适用于计算密集型任务(如 PDF 渲染、图像处理、数据并行计算)。 推荐使用 QtConcurrent::mapped / QtConcurrent::filtered 进行并行处理,比 QtConcurrent::run 更高效。 线程安全至关重要,共享数据必须用 QMutex 或 QMetaObject::invokeMethod 保护。 如果你的 PDF 渲染任务较重,方案 2(QtConcurrent::mapped + QFutureWatcher)是最佳选择,既能利用多核并行,又能保持代码简洁。 重新生成 使用QtConcurrent是多线程吗 多线程实现 ImageItem::setFilename 要将 ImageItem::setFilename 中的页面渲染操作放到多线程中,可以使用 Qt 的并发框架(QThreadPool 和 QRunnable)或者 QtConcurrent。以下是使用 QThreadPool 和 QRunnable 的实现方式: 方案一:使用 QRunnable 和 QThreadPool cpp #include <QRunnable> #include <QThreadPool> #include <QMutex> #include <QWaitCondition> class PageRenderRunnable : public QRunnable { public: PageRenderRunnable(PdfDocument* pdf, int pageIndex, QVector<QImage>* pageImages, QMutex* mutex) : m_pdf(pdf), m_pageIndex(pageIndex), m_pageImages(pageImages), m_mutex(mutex) {} void run() override { auto page = m_pdf->page(m_pageIndex); QImage image; if (!page.isValid()) { qDebug() << "Invalid page at index:" << m_pageIndex; image = QImage(); } else { image = page.image(2); // 渲染页面 if (image.isNull()) { qDebug() << "Failed to render page:" << m_pageIndex; image = QImage(); } } // 保护共享数据 QMutexLocker locker(m_mutex); if (m_pageIndex < m_pageImages->size()) { (*m_pageImages)[m_pageIndex] = image; } else { qWarning() << "Page index out of bounds:" << m_pageIndex; } } private: PdfDocument* m_pdf; int m_pageIndex; QVector<QImage>* m_pageImages; QMutex* m_mutex; }; void ImageItem::setFilename(QString filename) { m_pdf->loadFile(filename); m_bookmarkModel->clear(); getBookmarkList(m_pdf->bookmark(), m_bookmarkModel->invisibleRootItem()); // 初始化页面图像容器 int pageCount = m_pdf->pageCount(); m_pageImages.resize(pageCount); m_pageImages.fill(QImage()); // 先填充空图像 // 创建互斥锁保护共享数据 QMutex mutex; // 为每一页创建渲染任务 for (int i = 0; i < pageCount; ++i) { PageRenderRunnable* task = new PageRenderRunnable(m_pdf, i, &m_pageImages, &mutex); QThreadPool::globalInstance()->start(task); } // 等待所有任务完成(可选,如果不需要阻塞可以去掉) QThreadPool::globalInstance()->waitForDone(); Q_EMIT readyChanged(m_pdf->isValid()); } 方案二:使用 QtConcurrent(更简洁) cpp #include <QtConcurrent/QtConcurrent> void ImageItem::setFilename(QString filename) { m_pdf->loadFile(filename); m_bookmarkModel->clear(); getBookmarkList(m_pdf->bookmark(), m_bookmarkModel->invisibleRootItem()); int pageCount = m_pdf->pageCount(); m_pageImages.resize(pageCount); // 使用 QtConcurrent 映射 QFuture<void> future = QtConcurrent::run([this, pageCount]() { QVector<QImage> tempImages(pageCount); // 临时存储 QtConcurrent::blockingMap(tempImages, [this](QImage& image, int index) { auto page = m_pdf->page(index); if (!page.isValid()) { qDebug() << "Invalid page at index:" << index; image = QImage(); return; } image = page.image(2); if (image.isNull()) { qDebug() << "Failed to render page:" << index; image = QImage(); } }); // 完成后更新主线程数据 QMetaObject::invokeMethod(this, [this, tempImages]() { m_pageImages = tempImages; Q_EMIT readyChanged(m_pdf->isValid()); }, Qt::QueuedConnection); }); } 方案三:使用 QFutureWatcher 异步处理(推荐) cpp #include <QFutureWatcher> void ImageItem::setFilename(QString filename) { m_pdf->loadFile(filename); m_bookmarkModel->clear(); getBookmarkList(m_pdf->bookmark(), m_bookmarkModel->invisibleRootItem()); int pageCount = m_pdf->pageCount(); m_pageImages.resize(pageCount); m_pageImages.fill(QImage()); // 初始化为空图像 // 创建 FutureWatcher 来监控异步操作 QFutureWatcher<void>* watcher = new QFutureWatcher<void>(this); connect(watcher, &QFutureWatcher<void>::finished, [this, watcher]() { watcher->deleteLater(); Q_EMIT readyChanged(m_pdf->isValid()); // 这里可以添加渲染完成后的其他操作 }); // 启动异步任务 QFuture<void> future = QtConcurrent::run([this, pageCount]() { QVector<QImage> tempImages(pageCount); QtConcurrent::blockingMap(tempImages, [this](QImage& image, int index) { auto page = m_pdf->page(index); if (!page.isValid()) { qDebug() << "Invalid page at index:" << index; image = QImage(); return; } image = page.image(2); if (image.isNull()) { qDebug() << "Failed to render page:" << index; image = QImage(); } }); // 使用信号槽更新主线程数据 QMetaObject::invokeMethod(this, [this, tempImages]() { m_pageImages = tempImages; }, Qt::QueuedConnection); }); watcher->setFuture(future); } 注意事项 线程安全:PDF 文档对象 m_pdf 的方法是否是线程安全的?如果不是,需要在主线程中预先获取所有页面对象,然后传递到工作线程。 内存管理:大量图像渲染会消耗较多内存,考虑实现按需渲染或缓存机制。 进度报告:可以添加进度信号,让用户知道渲染进度。 取消机制:如果需要支持取消长时间运行的渲染任务,需要更复杂的实现。 性能优化:可以根据系统核心数调整并发渲染的页面数量。 推荐使用方案三,因为它提供了更好的异步处理能力,并且不会阻塞主线程,同时保持了代码的清晰性。 void ImageItem::setFilename(QString filename) { // QDateTime beginTime = QDateTime::currentDateTime(); m_pdf->loadFile(filename); m_bookmarkModel->clear(); // m_pdf->bookmark().debugMesg(); getBookmarkList(m_pdf->bookmark(), m_bookmarkModel->invisibleRootItem()); for (int i = 0/*start*/; i <= /*end*/m_pdf->pageCount(); i++) { auto page = m_pdf->page(i); if (!page.isValid()) { qDebug() << "Invalid page at index:" << i; m_pageImages.append(QImage()); // 存入空图片 continue; } QImage image = page.image(2); // 渲染页面 if (image.isNull()) { qDebug() << "Failed to render page:" << i; m_pageImages.append(QImage()); } else { m_pageImages.append(image); } } Q_EMIT readyChanged(m_pdf->isValid()); }实现多线程获取到m_pageImages
07-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值