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
I had a chat at Qt bugreports on the topic.
The noticed behavior, althoug not documented, is expected. Summarizing:
QtConcurrent::run()
is calledwaiForFinished()
is calledQtConcurrent::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.