Solved QProgressDialog freezes when the main thread is apparently non blocked
-
I am working on a project that uses a
QProgressDialog
during a time-consuming computation process. In order to not block the main UI thread, the class that does the computation is moved to a newQThread
and it is synchronized with the main thread using a start and finish signal connected usingQt::AutoConnection
, the default option forQObject::connect
. My problem is that theQProgressDialog
sometimes freezes as can be seen in the this video (also attached a gif version, but it is not as clear as the video):In this animation, you can see how after starting the project, the progress bar gets stuck at
Second check
. And the application stays like this until the mouse enters again theQProgressDialog
. This only happens eventually, and it is more prone to happen if you move the mouse over other windows. Moreover it never happens at the same stage.The main part of the software consist of a
StateMachine
that lives in the main thread and inheritsQObject
and interacts with the rest of widgets (in this case theQProgressDialog
):state_machine.h
#pragma once #include <QObject> class StateMachine : public QObject { Q_OBJECT public: static StateMachine* open(QThread* worker_thread, QWidget* parent); signals: void finished(); void datasetOpened(); void requestCloseDataset(); void errorMessage(QString text); void progressShow(); void progressHide(); void progressText(QString text); void requestProcess1(); void requestProcess2(); void requestProcess3(); void requestProcess4(); protected slots: void process1Done(); void process2Done(); void process3Done(); void process4Done(); protected: StateMachine(QThread* worker_thread, QWidget* parent = nullptr); };
state_machine.cpp
#include "state_machine.h" #include <QWidget> #include "progress_dialog.h" #include "state_machine_worker.h" StateMachine *StateMachine::open(QThread *worker_thread, QWidget *parent) { return new StateMachine(worker_thread, parent); } StateMachine::StateMachine(QThread *worker_thread, QWidget *parent) : QObject(parent) { // Connect connect(this, &StateMachine::finished, this, &QObject::deleteLater); // Create worker StateMachineWorker *worker = new StateMachineWorker(); worker->moveToThread(worker_thread); connect(this, &StateMachine::finished, worker, &QObject::deleteLater); /// Dataset file license connect(this, &StateMachine::requestProcess1, worker, &StateMachineWorker::process1); connect(worker, &StateMachineWorker::process1Done, this, &StateMachine::process1Done); /// Sonar list connect(this, &StateMachine::requestProcess2, worker, &StateMachineWorker::process2); connect(worker, &StateMachineWorker::process2Done, this, &StateMachine::process2Done); /// Sonar limit connect(this, &StateMachine::requestProcess3, worker, &StateMachineWorker::process3); connect(worker, &StateMachineWorker::process3Done, this, &StateMachine::process3Done); /// Dataset initialize connect(this, &StateMachine::requestProcess4, worker, &StateMachineWorker::process4); connect(worker, &StateMachineWorker::process4Done, this, &StateMachine::process4Done); // Create progress ProgressDialog *progress = ProgressDialog::create("OpenProject", "", "", 0, 0, parent, Qt::ApplicationModal); connect(this, &StateMachine::progressHide, progress, &QWidget::hide); connect(this, &StateMachine::progressShow, progress, &QWidget::show); connect(this, &StateMachine::finished, progress, &QWidget::hide); connect(this, &StateMachine::progressText, progress, &ProgressDialog::setLabelText); connect(this, &StateMachine::finished, progress, &QObject::deleteLater); // Check dataset file license emit progressText("First check"); emit progressShow(); emit requestProcess1(); } void StateMachine::process1Done() { emit progressText("Second check"); emit requestProcess2(); } void StateMachine::process2Done() { emit progressText("Third check"); emit requestProcess3(); } void StateMachine::process3Done() { emit progressText("Fourth check"); emit requestProcess4(); } void StateMachine::process4Done() { emit progressHide(); emit requestCloseDataset(); emit datasetOpened(); emit finished(); }
A
StateMachineWorker
that lives in a secondary thread and interacts with theStateMachine
and does the process that are thread blocking.state_machine_worker.h
#pragma once #include <QObject> class StateMachineWorker : public QObject { Q_OBJECT public: StateMachineWorker(QObject* parent = nullptr); signals: void process1Done(); void process2Done(); void process3Done(); void process4Done(); public slots: void process1(); void process2(); void process3(); void process4(); };
state_machine_worker.cpp
#include "state_machine_worker.h" #include <thread> StateMachineWorker::StateMachineWorker(QObject *parent) : QObject(parent) { } void StateMachineWorker::process1() { std::this_thread::sleep_for(std::chrono::seconds(2)); emit process1Done(); } void StateMachineWorker::process2() { std::this_thread::sleep_for(std::chrono::seconds(2)); emit process2Done(); } void StateMachineWorker::process3() { std::this_thread::sleep_for(std::chrono::seconds(2)); emit process3Done(); } void StateMachineWorker::process4() { std::this_thread::sleep_for(std::chrono::seconds(2)); emit process4Done(); }
The entire example can be found here. As you can see, the worker is moved to the thread to avoid blocking the main UI thread. However, as it can be seen in the previous video, the GUI becomes unresponsive regardless of the main thread non being blocked (or at least I think it is not blocked).
Do you have any idea on what am I doing wrong? How can I make the progress dialog not freeze? Should I report this as a bug? If I set the
QProgressDialog
toQt::NonModal
instead ofQt::ApplicationModal
the problem disappears, but I want to block the input to the other widgets, so changing that is not an option.This only happens on Windows and has been tested with Windows 10, MSVC 19.26.28806.0 and Qt Version 5.14.2. In Ubuntu with g++7 and Qt 5.9.5 it does not happen.
PS: I know that
StateMachine
and theStateMacineWorker
as it coulb be just an object that lives in the secondary thread and interacts directly with the main thread widgets through signals/slots rather than havingStateMachine
live in the main thread and interact with the worker, but this is not the main concern of this question. -
Can you please simplify your example - this looks to complicated for a reproducer so we can debug it. Maybe then you also find the reason why it stops. I would suggest you to add some debug outut about the current thread id.
-
The problem is that the simpler it gets the fewer chances that the progress dialog freezes. Which would make one think that the problem is somewhere in my code, and these type of problems are normally main thread is blocked. The code that I shared is the smallest example that I have been able to reproduce the issue, which only appears randomly. The non desired behaviour is still present and I am 100% sure that I am not blocking the main thread.
I know I am asking for an impossible, but I have been behind this for two weeks already and I am not a newbie, I have already gone through the basics of checking everything possible regarding main thread-secondary thread.
Obviously I will keep working on it and if I find a way to simplify it I will post an update. Thank you for your time! -
Hi,
Since you are mentioning two different Qt versions, two things you can do:
- Use a previous version of Qt on Windows to see if it's regression
- Use a more recent version of Qt on Linux to see if you can reproduce that
-
hi @apalomer
correct me, if I'm wrong here, but the disable of all other windows is, as far as I know, normal behavior for a QProgressDialog
like in this small example:
int main(int argc, char *argv[]) { QApplication app(argc, argv); QPushButton p("Spawn QProgressDialog"); p.show(); QObject::connect(&p,&QPushButton::clicked, [=]()->void{ QProgressDialog *progress = new QProgressDialog ("OpenProject", "Cancel", 0, 0, nullptr); progress->exec(); }); return app.exec(); }
-
@SGaist I downloaded all the versions of Qt to do exactly that on windows. Test if it is a version issue. I will get back after trying.
@J-Hilk disabeling all the other windows I believe is a consequence of the window modality. However, I find interesting from your example that you doprogress->exec();
instead ofprogress->show()
and let the application event handler handle everything from there on. Is it how it should be for dialogs that areQt::WindowModal
orQt::ApplicationModal
? -
@apalomer well, looking at the documentation:
https://doc.qt.io/qt-5/qdialog.html#execI would say, the recommended way is to call open() rather then exec or show :D
-
@SGaist I've tryed and in 5.15.0 it does not happen anymore.