multithreadingqtqtconcurrent

QtConcurred spawned threads have same ID as main thread


In my desktop software I'm starting several QtConcurrent threads from the main window. I've checked the thread IDs with QThread::currentThreadId() and noticed that they have the same ID as the GUI thread. Made some experiments, and noticed that the culprit is the waitForFinished() method. But in a quite strange way...

I wrote a minimal test, where I spawn a qtconcurrent on button click. The run method updates a counter on a spinbox.

#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QDebug>
#include <QThread>
#include <qtconcurrentrun.h>

MainWindow::MainWindow(QWidget *parent)
  : QMainWindow(parent)
  , ui_(new Ui::MainWindow)
  , cnt_(0)
{
  ui_->setupUi(this);
  connect(this, &MainWindow::setSpinBoxValue, ui_->spinBox, &QSpinBox::setValue);

  qDebug() << "MainWindow() thread ID: " << QThread::currentThreadId();
}

MainWindow::~MainWindow()
{
  qDebug() << "~MainWindow() thread ID: " << QThread::currentThreadId();

  delete ui_;
}

void MainWindow::on_pushButton_clicked()
{
  qDebug() << "on_pushButton_clicked() thread ID: " << QThread::currentThreadId();

  QFuture<void> th = QtConcurrent::run(this, &MainWindow::updateValue);

  // TEST MADE ENABLING/DISABLING THE FOLLOWING LINE
  th.waitForFinished();
}

void MainWindow::updateValue()
{
  qDebug() << "updateValue() thread ID: " << QThread::currentThreadId();

  emit setSpinBoxValue(++cnt_);
}

If I remove/comment th.waitForFinished() line the debug output shows different IDs for the click thread and the concurrent thread, as expected:

14:38:17: Debugging starts
MainWindow() thread ID:  0x1ad0
on_pushButton_clicked() thread ID:  0x1ad0
updateValue() thread ID:  0xc40
on_pushButton_clicked() thread ID:  0x1ad0
updateValue() thread ID:  0xc40
on_pushButton_clicked() thread ID:  0x1ad0
updateValue() thread ID:  0xc40
~MainWindow() thread ID:  0x1ad0
14:38:27: Debugging has finished

If I wait for thread termination (th.waitForFinished() called), the concurrent runs in a new thread the first time, then it runs on the main GUI thread on all successive call

14:40:23: Debugging starts
MainWindow() thread ID:  0x764
on_pushButton_clicked() thread ID:  0x764
updateValue() thread ID:  0x83c
on_pushButton_clicked() thread ID:  0x764
updateValue() thread ID:  0x764
on_pushButton_clicked() thread ID:  0x764
updateValue() thread ID:  0x764
~MainWindow() thread ID:  0x764
14:40:35: Debugging has finished

Why is this happening?

I suppose it has something to do with the fact that the waiting is done in the main thread. But once the wait is finished the software is free to create a new thread, so I don't know why this is happening.

I'm working on Windows 10, Qt 5.15.2 MSVC2019 x64


Solution

  • I had a chat at Qt bugreports on the topic.

    The noticed behavior, althoug not documented, is expected. Summarizing:

    This won't happen if multiple threads are created

    QFuture<void> th[2];
    for(unsigned int i = 0; i < 2; i++)
      th[i] = QtConcurrent::run(this, &MainWindow::updateValue, i);
    for(unsigned int i = 0; i < 2; i++)
      th[i].waitForFinished();
    

    In this case, Qt can't convert QtConcurrent::run() in a function call, otherwise the 2 threads won't run in parallel. So 2 new threads will be created (both updateValue() functions will be run in threads with ID different from main thread's ID).

    To fix the issue with the single thread, use a QFutureWatcher instead of waitForFinished(), that emits signal when the thread terminates; main thread won't be locked (will return on main thread when finished in a slot is connected to the signal), and new threads will be created on every QtConcurrent::run() call.