Separate QThread to update QDialog's UI



  • So, I was using QCoreApplication::processEvents() to update the UI of a QDialog, and I thought QThreads (even to familiarise with them) would have been better.
    I wrote a simple test, with a MainWindow with a unique QPushButton; pressing it, a QDialog is shown, with a qGraphicsView and two buttons, one to pause, and one to stop.
    If you don't want to read all this one, simply check my code.
    I have a class Algorithm that simulates a heavy task (a while loop, inside of which there are two things, a for loop simply printing two values and a function that randomly generates two values). Those two values are returned via a signal to the dialog, so that I can use them to draw a point on the qGraphicsView. Another signal (finished()) is emitted when the while loop ends (that is, when the conditions i < 10000 or running == true are false). Running is a private member variable of the class Algorithm. It is set to false if a user clicks on pause/stop on the dialog. So I have anothe signal, emitted by Dialog, caught by class Algorithm, that sets running to true or false.

    The problem arises since this last signal (setRunning(bool)) is always caught at the end of the loop, when the computations already are ended.
    So I added Qt::DirectConnection and this now is caught when the button is pressed.
    I wonder if there is a better way to do this.
    Thank you in advance.
    I copy here the code, if you don't want to read all the above :D

    //mainwindow.h
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    
    namespace Ui {
      class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
      Q_OBJECT
    
      public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
      private slots:
    	void on_pushButton_clicked();
    
      private:
    	Ui::MainWindow *ui;
    };
    #endif // MAINWINDOW_H
    

    MainWindow.cpp

    #include "dialog.h"
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    #include <QThread>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
      ui->setupUi(this);
    }
    
    MainWindow::~MainWindow()
    {
      delete ui;
    }
    
    void MainWindow::on_pushButton_clicked()
    {
      Dialog *d = new Dialog(this);
      d->setAttribute(Qt::WA_DeleteOnClose, true);
      d->show();
      d->doSomething();
    }
    

    dialog.h

    #ifndef DIALOG_H
    #define DIALOG_H
    
    #include "algorithm.h"
    
    #include <QDialog>
    #include <QGraphicsScene>
    #include <QThread>
    
    namespace Ui {
    	class Dialog;
    }
    
    class Dialog : public QDialog
    {
    		Q_OBJECT
    
    	public:
    		explicit Dialog(QWidget *parent = 0);
    		~Dialog();
    
    	private:
    		Ui::Dialog *ui;
    		QGraphicsScene* scene;
    		bool running;
    		int i, c;
    		Algorithm* a;
    
    	signals:
    		void setStatus(bool);
    
    	private slots:
    		void on_pushButton_clicked();
    		void on_pushButton_2_clicked();
    		void draw_point(int x, int y);
    
    	public slots:
    		void doSomething();
    };
    
    #endif // DIALOG_H
    

    dialog.cpp

    #include "algorithm.h"
    #include "dialog.h"
    #include "ui_dialog.h"
    
    #include <QDebug>
    #include <QThread>
    
    Dialog::Dialog(QWidget *parent) :
    	QDialog(parent),
    	ui(new Ui::Dialog)
    {
    	ui->setupUi(this);
    	scene = new QGraphicsScene(this);
    	scene->setSceneRect(0, 0, 200, 200);
    	ui->graphicsView->setScene(scene);
    	ui->graphicsView->setFixedSize(200, 200);
    
    	i = 0;
    	c = 0;
    
    	running = true;
    }
    
    Dialog::~Dialog()
    {
    	delete scene;
    	delete ui;
    }
    
    void Dialog::doSomething()
    {
    	if (c == 0)
    	{
    		QThread* t = new QThread;
    		a = new Algorithm(i, true);
    		a->moveToThread(t);
    		connect (a, SIGNAL(new_point(int,int)), this, SLOT(draw_point(int,int)));
    		connect (t, SIGNAL(started()), a, SLOT(compute()));
    		connect (this, SIGNAL(setStatus(bool)), a, SLOT(setRunning(bool)), Qt::DirectConnection);
    		connect (a, SIGNAL(finished()), t, SLOT(quit()));
    		connect (a, SIGNAL(finished()), a, SLOT(deleteLater()));
    		connect (t, SIGNAL(finished()), t, SLOT(deleteLater()));
    
    		connect (a, SIGNAL(paused()), t, SLOT(quit()));
    		t->start();
    	}
    	else
    	{
    		QThread* t = new QThread;
    		a->moveToThread(t);
    		connect (a, SIGNAL(new_point(int,int)), this, SLOT(draw_point(int,int)));
    		connect (t, SIGNAL(started()), a, SLOT(compute()));
    		connect (this, SIGNAL(setStatus(bool)), a, SLOT(setRunning(bool)), Qt::DirectConnection);
    		connect (a, SIGNAL(finished()), t, SLOT(quit()));
    		connect (a, SIGNAL(finished()), a, SLOT(deleteLater()));
    		connect (t, SIGNAL(finished()), t, SLOT(deleteLater()));
    
    		connect (a, SIGNAL(paused()), t, SLOT(quit()));
    		t->start();
    	}
    	c++;
    }
    
    void Dialog::on_pushButton_clicked()
    {
    	running = false;
    	ui->pushButton->setEnabled(false);
    	ui->pushButton_2->setEnabled(false);
    	emit setStatus(running);
    }
    
    void Dialog::on_pushButton_2_clicked()
    {
    	if (running)
    	{
    		running = false;
    		ui->pushButton_2->setText("Resume");
    		emit setStatus(running);
    	}
    	else
    	{
    		running = true;
    		ui->pushButton_2->setText("Pause");
    		emit setStatus(running);
    		doSomething();
    	}
    }
    
    void Dialog::draw_point(int x, int y)
    {
    	scene->addEllipse(x, y, 1.0, 1.0);
    }
    

    algorithm.h

    #ifndef ALGORITHM_H
    #define ALGORITHM_H
    
    #include <QObject>
    
    class Algorithm : public QObject
    {
    		Q_OBJECT
    	public:
    		Algorithm(const int &i, const bool& running);
    		~Algorithm();
    
    	private:
    		int i;
    		bool running;
    
    	signals:
    		void new_point(int, int);
    		void finished();
    		void paused();
    
    	public slots:
    		void setRunning(bool value);
    		void compute();
    };
    
    #endif // ALGORITHM_H
    

    algorithm.cpp

    #include "algorithm.h"
    
    #include <QDebug>
    
    Algorithm::Algorithm(const int &i, const bool &running)
    {
    	this->i = i;
    	this->running = running;
    }
    
    Algorithm::~Algorithm()
    {}
    
    void Algorithm::compute()
    {
    	while ((i < 10000) && running)
    	{
    		for (int j = 0; j < 10; j++)
    			qDebug() << i << j;
    
    		emit new_point(qrand() % 200, qrand() % 200);
    		i++;
    	}
    
    	if (i == 10000)
    		emit finished();
    	else
    		emit paused();
    }
    
    void Algorithm::setRunning(bool value)
    {
    	running = value;
    	qDebug() << "Running:" << running;
    	emit finished();
    }

  • Moderators

    @alogim said:

    So I added Qt::DirectConnection and this now is caught when the button is pressed.

    Please don't do that! That causes your your slot to run in the GUI thread instead of your Algorithm's thread.

    The problem arises since this last signal (setRunning(bool)) is always caught at the end of the loop

    That's because your long while() loop blocks the thread's event loop. When the event loop is blocked, the thread cannot process any signals.

    There are two ways to use QThread:

    1. Create a worker QObject (like your Algorithm). Transfer data from between the threads using signals and slots. Do not implement any long running while() loops or long-running functions in your worker QObject.
    2. Subclass QThread and reimplement QThread::run(). Transfer data from your GUI thread to your worker thread using mutex-protected variables (note: your QThread subclass can still emit signals). Do not implement any slots in your QThread subclass.

    Since you want to have a long-running while() loop, and you're not using signals and slots to transfer data (you only use signals to start/stop the computation), that means option #2 is more suitable for your use case.

    Furthermore, with option #2, you don't need to implement your own running member variable. Your GUI thread can just call QThread::requestInterruption(), and your while loop can query QThread::isInterruptionRequested() to check if it should break.



  • @JKSH said:

    Please don't do that! That causes your your slot to run in the GUI thread instead of your Algorithm's thread.

    I knew this problem, but it seemed the only way to make it work. Anyway, as you said, it is not a valid solution, it was just a test.

    1. Subclass QThread and reimplement QThread::run(). Transfer data from your GUI thread to your worker thread using mutex-protected variables (note: your QThread subclass can still emit signals). Do not implement any slots in your QThread subclass.

    Since you want to have a long-running while() loop, and you're not using signals and slots to transfer data (you only use signals to start/stop the computation), that means option #2 is more suitable for your use case.

    I did the following:
    dialog.h

    #ifndef DIALOG_H
    #define DIALOG_H
    
    #include <QDialog>
    #include <QGraphicsScene>
    #include "algorithm.h"
    
    namespace Ui {
    	class Dialog;
    }
    
    class Dialog : public QDialog
    {
    		Q_OBJECT
    
    	public:
    		explicit Dialog(QWidget *parent = 0);
    		~Dialog();
    
    	private:
    		Ui::Dialog *ui;
    		QGraphicsScene* scene;
    		algorithm* a;
    
    	public slots:
    		void draw_new_point(int x, int y);
    	private slots:
    		void on_pushButton_clicked();
    		void on_pushButton_2_clicked();
    };
    
    #endif // DIALOG_H
    

    dialog.cpp

    #include "dialog.h"
    #include "ui_dialog.h"
    
    Dialog::Dialog(QWidget *parent) :
    	QDialog(parent),
    	ui(new Ui::Dialog)
    {
    	ui->setupUi(this);
    	scene = new QGraphicsScene(this);
    	scene->setSceneRect(0, 0, 300, 300);
    	ui->graphicsView->setScene(scene);
    	ui->graphicsView->setFixedSize(300, 300);
    
    	a = NULL;
    }
    
    Dialog::~Dialog()
    {
    	delete scene;
    	delete ui;
    }
    
    void Dialog::draw_new_point(int x, int y)
    {
    	scene->addEllipse(x, y, 1.0, 1.0);
    }
    
    void Dialog::on_pushButton_clicked()
    {
    	a = new algorithm;
    
    	connect (a, SIGNAL(new_point(int,int)), this, SLOT(draw_new_point(int,int)));
    	connect (a, SIGNAL(finished()), a, SLOT(quit()));
    	connect (a, SIGNAL(finished()), a, SLOT(deleteLater()));
    
    	a->start();
    }
    
    void Dialog::on_pushButton_2_clicked()
    {
    	a->requestInterruption();
    }
    

    algorithm.h

    #ifndef ALGORITHM_H
    #define ALGORITHM_H
    
    #include <QThread>
    #include "algorithm.h"
    
    class algorithm : public QThread
    {
    		Q_OBJECT
    
    	public:
    		algorithm();
    
    		// QThread interface
    	protected:
    		void run();
    
    	signals:
    		void new_point(int, int);
    
    };
    
    #endif // ALGORITHM_H
    

    algorithm.cpp

    #include "algorithm.h"
    #include "unistd.h"
    
    algorithm::algorithm()
    {
    
    }
    
    void algorithm::run()
    {
    	for (int i = 0; i < 10000; i++)
    	{
    		if (isInterruptionRequested())
    			break;
    		emit new_point(qrand() % 300, qrand() % 300);
    	}
    }
    

    But still the graphicsView is not smoothly updated, but in blocks. If I add usleep(100000) after the break in the statement, everything works as expected, with qGraphicsView updating smoothly.


  • Moderators

    @alogim said:

    But still the graphicsView is not smoothly updated, but in blocks. If I add usleep(100000) after the break in the statement, everything works as expected, with qGraphicsView updating smoothly.

    If you don't have usleep(), then you will spam your GUI thread with lots and lots of signals in a very short amount of time. The GUI thread can't keep up, that's why if becomes non-smooth.



  • @JKSH said:

    @alogim said:

    But still the graphicsView is not smoothly updated, but in blocks. If I add usleep(100000) after the break in the statement, everything works as expected, with qGraphicsView updating smoothly.

    If you don't have usleep(), then you will spam your GUI thread with lots and lots of signals in a very short amount of time. The GUI thread can't keep up, that's why if becomes non-smooth.

    Yes, that seems to be a good explanation but... why, if I put the same loop (without usleep() in the dialog class and call processEvents() after each item addition (without using threads at all), does the qGraphicsView smoothly update?


  • Moderators

    @alogim said:

    Yes, that seems to be a good explanation but... why, if I put the same loop (without usleep() in the dialog class and call processEvents() after each item addition (without using threads at all), does the qGraphicsView smoothly update?

    Use QElapsedTimer to find out:

    1. How long does it take to emit the signal 10000 times without processEvents()?
    2. How long does it take to emit the signal 10000 times with processEvents()?

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.