QtConcurred spawned threads have same ID as main thread

284 Views Asked by At

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

2

There are 2 best solutions below

0
il_mix On BEST ANSWER

I had a chat at Qt bugreports on the topic.

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

  • QtConcurrent::run() is called
  • waiForFinished() is called
  • main thread need to stop witing for other thread to finish
  • but why bothering creting a new thread when the behavior is exactly like doing a function call? => Qt automatically converts QtConcurrent::run() in a function call (executed in main thread)

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.

1
eyllanesc On

It seems to be an unexpected behavior as it is not documented. Perhaps the key to understanding the cause of the problem is that by default QtConcurrent::run() uses QThreadPool::globalInstance() but if a different QThreadPool is created then this problem is not observed:

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

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

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