Qt 5 : update QProgressBar during QThread work via signal

11,234

Solution 1

Absolutely wrong using of QThread). See what is the correct way to implement a QThread... (example please...). You need to learn thread's basics.

Your mistakes:
1. Create a static thread object in a local scope;
2. Wait for its finish in the main thread;
3. Don't start the thread;
4. Direct call method doHeavyCaclulations() in the main thread;
5. emit signal without working event loop for its deliver...

For your purpose you need:
Don't inherit QThread. Just create simple Work class with the necessary function:

class Work: public QObject
{
    Q_OBJECT

public:
    Work(){};
    virtual ~Work(){};

public slots:
    void doHeavyCaclulations() { /* do what you need and emit progress signal */ };

signals: 
    void progress(int);                
}

// Then:
void QApp::doSomeWork()
{
    //...
    QThread* thread = new QThread(parent);
    Work* worker = new Work; // Do not set a parent. The object cannot be moved if it has a parent. 
    worker->moveToThread(thread);

    connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater()));
    connect(thread, SIGNAL(started()), worker, SLOT(doHeavyCaclulations()));
    connect(worker, SIGNAL(progress(int)), &progressDialog, SLOT(setValue(int)));

    thread->start();        
    //...
}

Solution 2

QThread has one very important thing you have to always remember when working with it - only the run() actually runs in a separate thread.

Whenever you create an instance of QThread this instance's thread affinity (the thread it belongs to) is the same thread where you have created it in. What's the big deal with that and what does it have to do with my slots and signals you may ask? Well, it has a lot to do with these things. Because only run() runs inside a separate thread you have to consider the following:

  1. Signals belong to the instance ergo signals have a different thread affinity then the run()
  2. Slots belong to the instance ergo slots have a different thread affinity then the run() - accessing shared data that is processed both inside a slot and inside run() requires explicitly employing thread-safety mechanisms such as mutexes and semaphores
  3. If you do a lot of stuff inside your slots you will still freeze your UI as if you are not using your QThread

That said there are some scenarios where you may want to/have to employ slots and signals in a QThread but such implementation would have to be directed towards controlling the instance of QThread and not what it's actually running in a separate thread (using run()).

Here is a small demo I have written as a demonstration of how to implement slots and signals and interact with a separate thread using QObject. It employs slots and signals. Note that the usage of QThread is actually not necessary. You can also use a QRunnable for example (though you have to explicitly tell it to inherit from QObject too or to use a separate subclass of QObject created by you because QRunnable doesn't support slots and signals (it's not a subclass of QObject).

The advantage of using a QObject is that you can move its instance to the thread that is change it's thread affinity so that it completely runs in that separate thread (slots included). You can also put multiple QObject instances inside a single QThread if you want to. When inheriting a QThread and using it instead of this model you are limiting your options quite a bit.

So my advice here is dump the QThread implementation and go for the QThread + QObject (also know as Worker design pattern) way of doing things (for this particular scenario that is).

Share:
11,234
remove before flight
Author by

remove before flight

Updated on June 18, 2022

Comments

  • remove before flight
    remove before flight almost 2 years

    I'm trying to update a QProgressDialog (owned by a QMainWindow class) along the execution of a QThread who process some time consuming operations. The thread emit some signals during operation in order to inform the calling app about progression. I'm looking to connect the progress signal emitted by the thread to the setValue slot of the QProgressDialog in order to update the progress bar.

    It doesn't work ! The progress dialog is not displayed. If I add a slot in my QMainWindow and connect it to the worker progress signal in order to display the value given by the thread throught qDebug output, I see that signals seems to be stacked during the threaded operation and unstacked only at the end of the thread.

    I have tryed the DirectConnection connect's option without any success.

    Here is my code : qapp.cpp

    #include "qapp.h"
    #include <threaded.h>
    
    #include <QVBoxLayout>
    #include <QPushButton>
    #include <QDebug>
    #include <QProgressDialog>
    
    QApp::QApp(QWidget *parent) :
        QMainWindow(parent)
    {
        QVBoxLayout *mainLayout = new QVBoxLayout(this);
        QWidget *window = new QWidget(this);
        window->setLayout(mainLayout);
        setCentralWidget(window);
        QPushButton *button = new QPushButton("Run");
        mainLayout->addWidget(button);
        connect(button, SIGNAL(clicked(bool)), this, SLOT(doSomeWork()));
    }
    
    void QApp::doSomeWork()
    {
        qDebug() << "do some work";
        Threaded worker;
        worker.doHeavyCaclulations();
    
        QProgressDialog progressDialog("Copying files...", "Abort Copy", 0, 10000, this);
        progressDialog.setWindowModality(Qt::WindowModal);
        progressDialog.setMinimumDuration(0);
        progressDialog.setValue(0);
    
        connect(&worker, SIGNAL(progress(int)), &progressDialog, SLOT(setValue(int)));
        connect(&worker, SIGNAL(progress(int)), this, SLOT(displayProgress(int)));
    
        worker.wait();
        qDebug() << "end of thread";
    }
    
    void QApp::displayProgress(int value)
    {
        qDebug() << "data received" << value;
    }
    
    QApp::~QApp()
    {
    
    }
    

    threaded.cpp :

    #include "threaded.h"
    #include <QDebug>
    
    Threaded::Threaded(QObject *parent) : QThread(parent)
    {
    
    }
    
    void Threaded::doHeavyCaclulations()
    {
        if (!isRunning())
        {
            qDebug() << "start thread"  ;
            start();
        }
    
    }
    
    void Threaded::run()
    {
        qDebug() << "running big loop";
        for(double k = 0 ; k < 10000 ; k++)
        {
            qDebug() << k;
            emit progress(k);
        }
    }
    

    qapp.h

    #ifndef QAPP_H
    #define QAPP_H
    
    #include <QMainWindow>
    
    class QApp : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit QApp(QWidget *parent = 0);
        ~QApp();
    
    private:
    
    private slots:
        void doSomeWork();
        void displayProgress(int value);
    };
    
    #endif // QAPP_H
    

    threaded.h

    #ifndef THREADED_H
    #define THREADED_H
    
    #include <QObject>
    #include <QThread>
    
    class Threaded : public QThread
    {
        Q_OBJECT
    public:
        explicit Threaded(QObject *parent = 0);
        void doHeavyCaclulations();
        void run();
    
    private:
    
    
    signals:
        void progress(int value);
    
    public slots:
    
    };
    
    #endif // THREADED_H
    

    The output of this code with k < 100 is :

    do some work
    start thread
    running big loop
    0
    1
    2
    3
    [...]
    97
    98
    99
    end of big loop
    end of thread
    data received 17
    data received 18
    data received 19
    [...]
    data received 99
    

    If I remplace worker.wait(); by

     int k=0;
        while(worker.isRunning())
        {
            qDebug() << "main " << k;
            k++;
        }
    

    I get outputs of the thread and output of the calling method interleaved. It confirm that my thread is independant of the calling method.

    Any idea about what I'm doing wrong ?

  • remove before flight
    remove before flight about 8 years
    Thank you for your answer ! Works fine this way !
  • remove before flight
    remove before flight about 8 years
    Thank you for your answer ! Good complement with Vladimir Bershov answer ! Works fine now !
  • rbaleksandar
    rbaleksandar about 8 years
    @removebeforeflight You're welcome. To his implementation don't forget the deleteLater() slots for the thread itself and the worker. This ensures proper deletion of both and also stopping the thread in a decent manner.