荷塘新雨 發表於 2026-1-5 10:23:59

Qt线程QtConcurrent模块的使用

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>前言</li><li>一、QtConcurrent::run()</li><li>二、QtConcurrent::mapped()</li><li>三、总结</li></ul></div><p class="maodian"></p><h2>前言</h2>
<p>在传统 Qt 多线程开发中,我们常通过继承 QThread 或使用 moveToThread 来实现后台任务。但这种方式需要手动管理线程生命周期、信号槽连接、对象所有权等,样板代码多、易出错。<br />而 Qt Concurrent 提供了一种更高层次的抽象:你只关心&ldquo;做什么&rdquo;,不关心&ldquo;在哪做&rdquo;。它基于线程池自动调度任务,极大简化了并发编程。<br />QtConcurrent是Qt特有的一个用于实现并发任务的模块,提供了一组基于任务(task-based)而非线程(thread-based)的并发API。它底层使用 QThreadPool + QRunnable 实现,自动管理线程池,你无需手动创建/销毁线程。如果有一个或多个短期简单任务会造成主线程的阻塞,又觉得为了他单独设计一个任务类再移动到子线程太过繁琐,我们可以使用QtConcurrent。</p>
<p class="maodian"></p><h2>一、QtConcurrent::run()</h2>
<p>QtConcurrent::run()是最简单的单任务线程实现。在写具体代码之前,我们需要先在pro文件中添加相应模块:</p>
<div class="jb51code"><pre class="brush:cpp;">QT += concurrent
</pre></div>
<p>然后添加头文件:</p>
<div class="jb51code"><pre class="brush:cpp;">#include &lt;QtConcurrent&gt;
</pre></div>
<p>和之前一样,通过点击界面按钮触发测试代码:</p>
<div class="jb51code"><pre class="brush:cpp;">int compute(int a, int b) {
    QThread::msleep(500);
    return a + b;
}

void MainWindow::on_btn_thread_start_3_clicked()
{
    static int a = 10, b = 20;
    a++;
    b++;

    // 1️⃣ 启动异步任务
    QFuture&lt;int&gt; future = QtConcurrent::run(compute, a, b);

    // 2️⃣ 创建 watcher 监听完成事件
    QFutureWatcher&lt;int&gt; *watcher = new QFutureWatcher&lt;int&gt;(this);
   
    // 3️⃣ 连接 finished 信号(在主线程触发!)
    connect(watcher, &amp;QFutureWatcher&lt;int&gt;::finished, this, () {
      qDebug() &lt;&lt; "Result:" &lt;&lt; watcher-&gt;result();
      watcher-&gt;deleteLater(); // 自动清理内存
    });

    // 4️⃣ 将 future 绑定到 watcher
    watcher-&gt;setFuture(future);
}
</pre></div>
<p>启动任务的时候,需要传递任务函数和参数:</p>
<div class="jb51code"><pre class="brush:cpp;">QFuture&lt;int&gt; future = QtConcurrent::run(compute, a, b);
</pre></div>
<p>返回值是第一个可监听的对象,代表一个未来会得到的的结果。<br />这一步是同步不阻塞的,随后我们要马上对future对象进行观察。注意QFuture本身是没有信号的,因此无法使用信号槽。所以我们要单独设置一个QFutureWatcher来进行观察,最后绑定future。代码比较简单,就不详细说了。注意watcher要在最终的槽函数中析构哦。<br />可以看到compute任务函数中,除了简单的加法运算之外,还特意做了线程延时。<br />最后,我们运行代码:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010510230567.png" /></p>
<p>每次点击都会叠加运算,并且界面完全不阻塞。<br />这种写法特别适合做简单但有可能造成阻塞的任务,相比于特意重载QThread或使用moveToThread,简直是清爽多了。(以前工作不太懂,所以也没用过,实在可惜。)<br />结合我过往的开发经验,说几个可能的应用场景:<br />(1)批量加载:本地有多个图片文件需要加载读取,甚至还要进行尺寸压缩<br />(2)多点绘图:QImage中需要绘制多线段,这常常伴随鼠标滑动绘图,为了不阻塞主线程影响流畅度,应当考虑线程。</p>
<p class="maodian"></p><h2>二、QtConcurrent::mapped()</h2>
<p>除此之外,QtConcurrent也提供了其他的方式,比如QtConcurrent::mapped()(并行转换)和 QtConcurrent::filtered()(并行过滤)。<br />我尝试了mapped的方式,它是一种批量的线程实现,在某些场合下挺实用的。<br />接下来,尝试一下图片的批量加载,并进行灰度转换,最后弹窗显示出来。<br />代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">QImage convertToGrayscale(const QString&amp; filePath)
{
    // 模拟阻塞
    QThread::msleep(2000);
    QImage img(filePath);
    if (img.isNull()) {
      qWarning() &lt;&lt; "Failed to load:" &lt;&lt; filePath;
      return QImage(); // 返回空图
    }
    // 转为黑白(灰度)
    return img.convertToFormat(QImage::Format_Grayscale8);
}

void MainWindow::on_btn_thread_start_3_1_clicked()
{
    // 1. 选择多个图片文件(最多10个)
    QStringList filePaths = QFileDialog::getOpenFileNames(
      this,
      "选择图片(最多10张)",
      QDir::homePath(),
      "Images (*.png *.jpg *.jpeg *.bmp *.gif)"
      );

    if (filePaths.isEmpty()) {
      return;
    }

    if (filePaths.size() &gt; 10) {
      QMessageBox::warning(this, "警告", "最多只能选择10张图片!");
      filePaths = filePaths.mid(0, 10);
    }

    // 2. 禁用按钮,防止重复点击
    // m_convertButton-&gt;setEnabled(false);

    // 3. 启动并发转换
    QFuture&lt;QImage&gt; future = QtConcurrent::mapped(filePaths, convertToGrayscale);

    // 4. 设置 watcher 监听完成
    if (!m_watcher) {
      m_watcher = new QFutureWatcher&lt;QImage&gt;(this);
      connect(m_watcher, &amp;QFutureWatcher&lt;QImage&gt;::finished,
                this, &amp;MainWindow::onConversionFinished);
    }
    m_watcher-&gt;setFuture(future);
}

void MainWindow::onConversionFinished()
{
    // 5. 获取所有结果(顺序与输入 filePaths 一致)
    QList&lt;QImage&gt; results = m_watcher-&gt;future().results();

    // 6. 为每张有效图片弹窗显示
    int validCount = 0;
    for (int i = 0; i &lt; results.size(); ++i) {
      if (!results.isNull()) {
            QString title = QString("结果 %1").arg(++validCount);
            showImageInDialog(results, title);
      }
    }

    // 7. 恢复按钮
    // m_convertButton-&gt;setEnabled(true);

    if (validCount == 0) {
      QMessageBox::information(this, "提示", "没有成功加载任何图片。");
    }
}

void MainWindow::showImageInDialog(const QImage &amp;img, const QString &amp;title)
{
    QDialog* dialog = new QDialog(this);
    dialog-&gt;setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动 delete
    dialog-&gt;setWindowTitle(title);
    dialog-&gt;resize(600, 400);

    QLabel* label = new QLabel(dialog);
    label-&gt;setPixmap(QPixmap::fromImage(img).scaled(
      dialog-&gt;size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
    label-&gt;setAlignment(Qt::AlignCenter);

    QVBoxLayout* layout = new QVBoxLayout(dialog);
    layout-&gt;addWidget(label);
    dialog-&gt;setLayout(layout);

    dialog-&gt;show();
}
</pre></div>
<p>我们关注一下这里:</p>
<div class="jb51code"><pre class="brush:cpp;">QFuture&lt;QImage&gt; future = QtConcurrent::mapped(filePaths, convertToGrayscale);
</pre></div>
<p>先是参数,后是接口,这一点与run有区别。接口的参数是QString,但传递的参数却是QStringList,也就是说qt会自动帮我们适应,处理批量线程。可以看到返回值依旧是QFuture,它的参数是QImage,而不是批量的<code>QList&lt;QImage&gt;</code>,这一点要注意哦。<br />同样的任务函数做了一点延时模拟阻塞,顺便转换了下灰度。<br />最后就是完成的槽函数,这代表所有任务都已经完成。</p>
<div class="jb51code"><pre class="brush:cpp;">QList&lt;QImage&gt; results = m_watcher-&gt;future().results();
</pre></div>
<p>返回值可以通过results接口获取。之后就是常规的弹窗,就不多解释了。<br />运行代码,查看结果:</p>
<p style="text-align:center"><img alt="" height="653" src="https://img.jbzj.com/file_images/article/202601/2026010510230561.jpg" width="1200" /></p>
<p>在延时的两秒时间内,界面完全没有被阻塞,随后三个弹窗一起弹出来,效果还是很好的。</p>
<p class="maodian"></p><h2>三、总结</h2>
<p>有关QtConcurrent的其他接口测试就先忽略了。光是run和mapped两种方式,我就已经觉得非常实用了。</p>
<p>以下是QtConcurrent和QThread+Worker的对比:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010510230539.png" /></p>
<p>可以看到,QtConcurrent更适合用作一次性的计算任务,特别是简单一次性的并行任务。如果是需要长期运行的任务,如while循环获取,还是采用QThread+Worker比较好。</p>
<p>到此这篇关于Qt线程QtConcurrent模块的使用的文章就介绍到这了,更多相关Qt QtConcurrent模块内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Qt使用QtConcurrent::run实现异步等待和同步调用</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Qt线程QtConcurrent模块的使用