Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QApplication thread freezes because of another QThread

In my Qt application I create a QThread that should perform some heavy calculation task regularly. Main QApplication thread is supposed to maintain a GUI (not included in example) and perform some regular updates as well. Both threads have their own timers to enable regular update() calls.

Problem: When calculation workload for worker thread exceeds some critical value my main thread stops receiving timer events.

Example code is below. It outputs "Main" when update() is called for main thread, and "Worker" for worker thread. If you run it you'll see that "Worker" is printed regularly and "Main" appears exactly two times (one at the beginning, and one in about 5 seconds). In case of full-featured GUI application this would effectively mean total GUI freeze.

Some observations.

  1. Reducing workload by putting 100 limit on inner cycle (instead of 1000) will fix the problem (both update() methods will be called regularly).
  2. Setting connection type for worker thread timer signal to Qt::DirectConnection will fix the problem.

So, as you can see I have a couple of workarounds on this, but I would appreciate anybody explaining me what's the problem with the original code. I expect threads to execute their event loops independently. I know that I'm blocking the worker thread event loop by a long update() operation but why in the world does it affect the main thread?

P.S. Yes, I know about QConcurrent alternative. But I'd just like to understand.

test.cpp

#include <windows.h>
#include <QApplication>

#include "test.h"

HANDLE mainThread_ = INVALID_HANDLE_VALUE;
QApplication *app_ = 0;
MyObj *obj_ = 0;
MyThread *thread_ = 0;

MyObj::MyObj()
    : timer_(0)
{
    timer_ = new QTimer(0);
    connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
    timer_->start(10);
}

void MyObj::update()
{
    printf("Main\n");
}

void MyThread::run()
{
    timer_ = new QTimer(0);
    connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
    timer_->start(10);

    exec();
}

void MyThread::update()
{
    printf("Worker\n");

    // do some hard work
    float f = 0.f;
    for (int i=0; i < 100000; ++i)
    {
        for (int j=0; j < 1000; ++j)
        {
            f += i * j;
        }
    }
}

int main()
{
    int argc = 0;
    app_ = new QApplication(argc, 0);

    obj_ = new MyObj();
    thread_ = new MyThread();
    thread_->start();

    QApplication::exec();

    return 0;
}

test.h

#include <QTimer>
#include <QThread>

class MyObj : public QObject
{
    Q_OBJECT

public:
    MyObj();

public slots:
    void update();

private:
    QTimer *timer_;
};

class MyThread : public QThread
{
    Q_OBJECT

public:
    void run();

public slots:
    void update();

private:
    QTimer *timer_;
};

UPD: I've got some answers from respectable members (read them below). Now I'd like to clarify what faulty idea broke my code in particular.

As you can see the plan was having two threads each running some update() procedure regularly. My mistake was thinking about update() as just some procedure, and it is a slot. A slot of particular object which has its own thread affinity, meaning that it's body will be executed in that thread (unless signal is dispatched with Qt::DirectConnection). Now, it appears that I've done it all right with timers -- each of them belongs to different thread -- but messed things up with update(). So I ended up executing both update() procedures in the main thread. Apparently at some point event loop becomes flooded with timer events and never finishes the iteration.

As for solutions. If you've read "You're doing it wrong" (and you should indeed) you know that it's rather convenient to have all your logic implemented in an object that is not subclassed from QThread but created separately and attached to QThread with moveToThread(). Personally I see nothing wrong with subclassing from QThread if you keep in mind that your object only controls the thread but doesn't belong to it. So it's not the place for the code you want to be executed in that thread.

like image 200
Anton Zherzdev Avatar asked Oct 22 '13 12:10

Anton Zherzdev


2 Answers

The first problem here is that you're inheriting from QThread, so as it states here, "you're doing it wrong".

The issues you're having stem from thread affinity (which thread an object is running on). For example, if you were to inherit from QThread and create objects in the constructor, without parenting the object, that object will be running in the main thread, not the new thread. So in the MyThread constructor you'd have:-

MyThread::MyThread()
    : timer_(0)
{
    timer_ = new QTimer(0);
    connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
    timer_->start(10);
}

The timer (timer_) here will be running on the main thread, not the new thread. To save repeating myself, one of my previous answers explains thread affinity here.

The best way to solve the problem is to change your class to inherit from QObject and then move that object to a new thread.

like image 64
TheDarkKnight Avatar answered Sep 28 '22 05:09

TheDarkKnight


first of all ... http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/

from your main i an see nothing like a GUI ... you just call the QApplication::exec() for whatever reason not app_->exec() (?)

for your threading problem: you can create a QObject derived class that has the slot doUpdate() or so. with this you can do it like:

TheUpdateObject* obj = new TheUpdateObject;
obj->moveToThread(thread_);  // thread_ is a QThread object
thread_->start();
connect(thread_, SIGNAL(finished()), obj, SLOT(deleteLater()));
QTimer* tmr = new QTimer(this);
tmr->setTimeout(10);
connect(tmr, SIGNAL(timeout()), obj, SLOT(doUpdate()));
connect(tmr, SIGNAL(timeout()), tmr, SLOT(start()));
tmr->start();

so the timer should restart itself and the doUpdate() should be called in the other thread.

inside the GUI you do not need to check for updates, the qt framework should redraw when needed.

like image 33
Zaiborg Avatar answered Sep 28 '22 04:09

Zaiborg