2

I'm running some tests in order to undestand what is the best way to update the GUI of a QDialog in a separate thread. I did the following:

  • main.cpp (untouched)

    #include "dialog.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        Dialog w;
        w.show();
    
        return a.exec();
    }
    
  • dialog.h: the SLOT draw_point(QPointF) is connected to the signal emitted by the worker class, in order to update the scene; int he UI there are only two buttons, a Start button that obviously starts the computation, and a Stop button that interrupts the computation.

    #ifndef DIALOG_H
    #define DIALOG_H
    
    #include <QDialog>
    #include <QElapsedTimer>
    #include <QGraphicsScene>
    #include <QThread>
    #include "worker.h"
    
    namespace Ui {
        class Dialog;
    }
    
    class Dialog : public QDialog
    {
            Q_OBJECT
    
        public:
            explicit Dialog(QWidget *parent = 0);
            ~Dialog();
    
        private:
            Ui::Dialog *ui;
            QGraphicsScene scene;
            QThread t;
            QElapsedTimer chronometer;
            worker work;
    
        public slots:
            void draw_point(QPointF p);
    
        private slots:
            // for Start button
            void on_pushButton_clicked();
    
            // for Stop button
            void on_pushButton_2_clicked();
            void print_finished();
    };
    
    #endif // DIALOG_H
    
  • dialog.cpp

    #include "dialog.h"
    #include "ui_dialog.h"
    
    #include <QDebug>
    #include <QElapsedTimer>
    #include <QTimer>
    
    Dialog::Dialog(QWidget *parent) :
        QDialog(parent),
        ui(new Ui::Dialog)
    {
        qDebug() << "Starting dialog thread" << thread();
    
        ui->setupUi(this);
    
        scene.setSceneRect(0, 0, 400, 400);
    
        ui->graphicsView->setScene(&scene);
        ui->graphicsView->setFixedSize(400, 400);
    
        work.moveToThread(&t);
    
        connect(&work, SIGNAL(new_point(QPointF)), this, SLOT(draw_point(QPointF)));
        connect(&t, SIGNAL(started()), &work, SLOT(doWork()));
        connect(&work, SIGNAL(finished()), &t, SLOT(quit()));
        connect(&work, SIGNAL(finished()), this, SLOT(print_finished()));
    }
    
    Dialog::~Dialog()
    {
        delete ui;
    }
    
    void Dialog::draw_point(QPointF p)
    {
        scene.addEllipse(p.x(), p.y(), 1.0, 1.0);
    }
    
    // Start button
    void Dialog::on_pushButton_clicked()
    {
        t.start();
        chronometer.start();
    }
    
    // Stop button
    void Dialog::on_pushButton_2_clicked()
    {
        work.running = false;
    }
    
    void Dialog::print_finished()
    {
        qDebug() << "Finished dialog thread" << thread();
        qDebug() << "after" << chronometer.elapsed();
    }
    
  • worker.h

    #ifndef WORKER_H
    #define WORKER_H
    
    #include <QObject>
    #include <QPointF>
    #include <QVector>
    
    class worker : public QObject
    {
            Q_OBJECT
        public:
            explicit worker();
    
            bool running;
    
        signals:
            void new_point(QPointF);
            void finished();
    
        public slots:
            void doWork();
    };
    
    #endif // WORKER_H
    
  • worker.cpp

    #include "worker.h"
    
    #include <QDebug>
    #include <QElapsedTimer>
    #include <QPoint>
    #include <QTimer>
    #include <unistd.h>
    
    worker::worker()
    {
        qDebug() << "Worker thread" << thread();
        running = true;
    }
    
    void worker::doWork()
    {
        qDebug() << "starting doWork thread" << thread();
    
        int i = 0;
        QVector<QPoint> v;
        QElapsedTimer t;
        t.start();
    
        while ((i < 100000) && running)
        {
            int x = qrand() % 400;
            int y = qrand() % 400;
            QPoint p(x, y);
    
            bool f = false;
            for (int j = 0; j < v.size() && !f; j++)
                if (v[i].x() == p.x() && v[i].y() == p.y())
                    f = true;
    
            if (!f)
            {
                emit new_point(p);
                i++;
            }
        }
    
        qDebug() << "elapsed time:" << t.elapsed();
        qDebug() << "closing doWork thread" << thread();
        emit finished();
    }
    
  • PROBLEMS:

  • The signal new_point is emitted too fast, hence the scene is not able to keep up updating it, hence the scene is updated in blocks. the only way to make update it smoothly seems to be by adding a usleep(100000) in the for loop, but I don't want to do this, since I really think it is a bad practice.

  • Checking values in the console as regards the elapsed time in the doWork() method and in the Qdialog thread, it seems that the for loop executes very fast, often in less than 100 milliseconds. The Qdialog thread takes instead much more time to process all the updates, that is, to draw all the points to the scene. Is there a better way to update the scene? I read on some forums to create a QImage and then pass it to the scene, could you provide a simple example for my case?

  • I can also use QCoreApplication::processEvents() and do all computations in the GUI thread, and in fact the GUI is responsive, the scene updates smoothly. But the time required to draw all the points is much much more than the time required to draw them with a separate thread. So, what should I do? Thank you in advance.

Michael
  • 876
  • 9
  • 29

0 Answers0