1

I cannot produce a very simple example to getting start with Qt multi-thread. I read a lot of posts and tutorials but still it doesn't work.

Goal

Have a background worker independent from the GUI. Oh, wow...

What I did

A simple example:

  • create the Engine class
  • that shows a QMainWindow
  • and starts a QTimer that prints a number

But if you left-click the title bar of the GUI, keeping pressed the mouse button (i.e. on the minimize button) the counter will stop! Even if it was created in a non-GUI environment and it was moved in another thread!

Why?

main.cpp

#include "engine.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Engine e;

    return a.exec();
}

engine.h

#ifndef ENGINE_H
#define ENGINE_H

#include <QObject>
#include <QThread>
#include <QTimer>

#include "mainwindow.h"

class Engine : public QObject
{
    Q_OBJECT

public:
    explicit Engine(QObject *parent = 0);

private:
    MainWindow mainWindow;
    QThread *thread;
    QTimer *timer;

private slots:
    void foo();

};

#endif // ENGINE_H

engine.c

#include "engine.h"
#include <QDebug>

Engine::Engine(QObject *parent) : QObject(parent)
{

    thread = new QThread(this);
    timer = new QTimer();
    timer->setInterval(100);
    connect(timer, &QTimer::timeout, this, &Engine::foo);
    connect(thread, &QThread::started, timer, static_cast<void (QTimer::*)(void)>(&QTimer::start));
    timer->moveToThread(thread);
    thread->start();

    mainWindow.show();
}

void Engine::foo()
{
    static int i;
    qDebug() << ++i;
}

The QMainWindow contains no code.

Mark
  • 4,338
  • 7
  • 58
  • 120

2 Answers2

5

Basically, Qt has one thread dealing with the GUI (typically the main thread). Any objects specific to this thread will be blocked by GUI work. You need to keep the GUI outside of your interacting partners.

To be more specific, your Engine object resides in the GUI/main thread. Even while your timer is sent to a worker thread, its signals are dispatched to the slot foo() in the main thread.

You need to de-mangle Engine and the main window such that Engine can reside in its own thread and process signals while the GUI is blocking.

ypnos
  • 50,202
  • 14
  • 95
  • 141
  • How does the `Engine` object might reside in the GUI thread, if is `Engine` itself that creates the GUI (`QMainWindow`)? I bet my mistakes rely on this (wrong?) assumption! Would you please help me to understand how to "de-mangle" those objects? I.e. in main.cpp, create Engine end move it to another thread, while keep the GUI in main() – Mark Aug 07 '17 at 14:27
  • Yes, you can do it in `main()` or from another object. Basically, don't send the QTimer to another thread, but a custom object that will then instantiate the timer and process results. Note that in general it is fine (and common) to send signals back to the GUI thread and display results accordingly (when the GUI is ready). – ypnos Aug 07 '17 at 15:29
  • Ok, I've tried to move the whole `Engine` object to another thread in main.cpp, where I also create the GUI. It works! – Mark Aug 07 '17 at 17:38
1

Looks like you moved QTimer instance to your custom thread, but you didnt move Engine instance to this thread, therefore, foo slot of Engine class will be executed in main thread.

I can suggest you using additional helping QObject-derived class instance within Engine class and move it to your new thread in Engine constructor.

With following changes solution works fine even with minimize button pressed (I've commented places where i added or changed anything. Also added new QObject-derived class EngineWorker):

Engine.h

#ifndef ENGINE_H
#define ENGINE_H

#include <QObject>
#include <QThread>
#include <QTimer>

#include "mainwindow.h"
#include "engineworker.h"

class Engine : public QObject
{
    Q_OBJECT

public:
    explicit Engine(QObject *parent = 0);

private:
    MainWindow mainWindow;
    QThread *thread;
    QTimer *timer;

    //additional QObject-derived class
    EngineWorker *worker;

private slots:
    void foo();

};

#endif // ENGINE_H

Engine.cpp

#include "engine.h"
#include <QDebug>


Engine::Engine(QObject *parent) : QObject(parent)
{
      thread = new QThread(this);
      timer = new QTimer();

      //Creating instance of Engine worker
      worker = new EngineWorker();

      timer->setInterval(100);

      //Connecting Engine worker' foo slot to timer
      connect(timer, &QTimer::timeout, worker, &EngineWorker::foo);
      connect(thread, &QThread::started, timer, static_cast<void (QTimer::*)(void)>(&QTimer::start));
      timer->moveToThread(thread);
      //Moving worker to custom thread
      worker->moveToThread(thread);

      thread->start();

      mainWindow.show();
}

void Engine::foo()
{
    static int i;
    qDebug() << ++i;
}

EngineWorker.h

#ifndef ENGINEWORKER_H
#define ENGINEWORKER_H

#include <QObject>
#include <QDebug>

class EngineWorker : public QObject
{
    Q_OBJECT
public:
    explicit EngineWorker(QObject *parent = 0);

signals:

public slots:
    void foo();
};

#endif // ENGINEWORKER_H

EngineWorker.cpp

#include "engineworker.h"

EngineWorker::EngineWorker(QObject *parent) : QObject(parent)
{

}

//foo slot of EngineWorker class
void EngineWorker::foo()
{
    static int j;
    qDebug() <<"Worker: "<< ++j;
}
  • Im trying your solution right now. In the meanwhile, let me ask you something: what are the advantages to create another object (`EngineWorker`) to move to the other thread, instead of directly move `Engine` itself from main as ypnos suggested? – Mark Aug 07 '17 at 17:24
  • 1
    @Mark I've just wrote modifications to your code to clarify the reason your code didnt work the way you wanted it to work. The way the final architecture should be depends on specific task you want to accomplish. Maybe moving Engine instance to different thread would be even more convenient for your case. – Jeembo Jones Aug 07 '17 at 17:46