Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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 new QThread and it is synchronized with the main thread using a start and finish signal connected using Qt::AutoConnection, the default option for QObject::connect. My problem is that the QProgressDialog sometimes freezes as can be seen in the this video (also attached a gif version, but it is not as clear as the video):

    enter image description here

    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 the QProgressDialog. 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 inherits QObject and interacts with the rest of widgets (in this case the QProgressDialog):

    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 the StateMachine 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 to Qt::NonModal instead of Qt::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 the StateMacineWorker 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 having StateMachine live in the main thread and interact with the worker, but this is not the main concern of this question.


  • Qt Champions 2019

    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!


  • Lifetime Qt Champion

    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

  • Moderators

    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 do progress->exec(); instead of progress->show() and let the application event handler handle everything from there on. Is it how it should be for dialogs that are Qt::WindowModal or Qt::ApplicationModal?


  • Moderators

    @apalomer well, looking at the documentation:
    https://doc.qt.io/qt-5/qdialog.html#exec

    I 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.


Log in to reply