PDA

View Full Version : Workload in a QThread blocks main application's event loop ?



0xBulbizarre
19th March 2006, 17:32
Hello, I'm trying to implement a simple gui with a worker thread using Qt 4.1.1

My gui is managed in the QApplication's event loop. The worker thread also has its event loop.

The worker thread starts some work when the GUI emits a defined signal (Qt::QueuedConnection because signal->slot are in different threads).

(of course,) the worker thread interrupts its event loop to do its job. The worker thread emits signals back to the gui thread to report progress, and that on a regular basis (to display a nice QProgressBar). The worker thread doesn't return to it's event loop until the job is finished.

The problem: my GUI thread does not process any event (coming from the worker thread) until the worker thread finishes its job and returns to its event loop.

Same problem in the other direction. When the user quits the programm, the GUI emits a signal ("request finish") to the worker thread so that it finishes, the GUI then wait() for the worker thread's termination. But the worker thread never handles the "request finish" signal until the GUI thread returns to its event loop. The code below illustrates that and locks both threads.



// GUI code, this is a slot; auto connected by moc.
void on_exitButon_clicked(void) {
QTimer::singleShot(0, workerThread, SLOT(stopThread(void)));
workerThread->wait();
close();
}


This is really strange, I already worked with threads, but never in Qt. It seems here that when a thread blocks the other are not scheduled.

Thanks for your time!

jacek
19th March 2006, 18:02
// GUI code, this is a slot; auto connected by moc.
void on_exitButon_clicked(void) {
QTimer::singleShot(0, workerThread, SLOT(stopThread(void)));
workerThread->wait();
close();
}
This slot won't be triggered, because you block the event loop with "workerThread->wait()" and timer event can't be processed. Try sending an event to that thread.

0xBulbizarre
19th March 2006, 19:45
This slot won't be triggered, because you block the event loop with "workerThread->wait()" and timer event can't be processed. Try sending an event to that thread.

Ok, thank you for that one.

In fact, I wanted to avoid a connect() to do only 1 emit, that's why I used the one shot timer. Is there a way (1 liner) to trigger a slot of another thread?

You can also certainly explain me that one. Similar problem as describved above, but in the worker thread. The threadProgressing() signal (Qt::QueuedConnection) is only received by the GUI thread when the worker thread returns to the event loop. Is emit effectively only happening in the event loop?



// code executed by workerThread
// this is the slot called from the GUI thread (see post above)
void OtherThread::stopThread(void)
{
starting = false;
for (int i = 100; i > 0; i--) {
msleep(100);
emit threadProgressing(i);
// uncommenting following line solves the problem, what is the best practice here ?
//QCoreApplication::processEvents();
}
emit threadStopped();
quit();
}


Thank you

jacek
19th March 2006, 20:26
Is there a way (1 liner) to trigger a slot of another thread?
You could try to send QMetaCall event, but it probably won't fit in one line. Maybe you could add a boolean flag and some method that sets it when thread should stop itself?


The threadProgressing() signal (Qt::QueuedConnection) is only received by the GUI thread when the worker thread returns to the event loop. Is emit effectively only happening in the event loop?
The docs say:
With queued connections, the slot is invoked when control returns to the event loop of the thread to which the object belongs. The slot is executed in the thread where the receiver object lives.
Unless something has changed in Qt 4.1.1, sender doesn't even need to live in a thread with a running event loop (see the Mandelbrot example --- there's no event loop running in RenderThread).

0xBulbizarre
19th March 2006, 20:59
The threadProgressing() signal (Qt::QueuedConnection) is only received by the GUI thread when the worker thread returns to the event loop. Is emit effectively only happening in the event loop?



The docs say:


With queued connections, the slot is invoked when control returns to the event loop of the thread to which the object belongs. The slot is executed in the thread where the receiver object lives.
Unless something has changed in Qt 4.1.1, sender doesn't even need to live in a thread with a running event loop (see the Mandelbrot example --- there's no event loop running in RenderThread).

My problem here is that (it seems) the signal is not sent until control returns to the event loop of the _sender_, although the receiver is in its event loop during all that time.

jacek
19th March 2006, 21:36
My problem here is that (it seems) the signal is not sent until control returns to the event loop of the _sender_, although the receiver is in its event loop during all that time.
Do you actually start that second thread?

0xBulbizarre
19th March 2006, 21:56
Do you actually start that second thread?

Yes (of course), the second's thread run() method is :

void run()
{
exec();
}

Do you want some test code to experiment with, do you need more infos ?

jacek
19th March 2006, 22:17
Yes (of course), the second's thread run() method is :
That's the run() method, but how do you start that thread?

0xBulbizarre
19th March 2006, 22:26
That's the run() method, but how do you start that thread?

I start it from gui thread with


otherThread = new OtherThread(this);
otherThread->start();


OtherThread's declaration:

class OtherThread : public QThread

jacek
19th March 2006, 22:55
otherThread = new OtherThread(this);
otherThread->start();
Looks OK, did you try with 0 instead of "this"?

0xBulbizarre
20th March 2006, 09:37
Looks OK, did you try with 0 instead of "this"?

yes, but nothing changes (apparently).


I think I found something... The slot executed by the singleShot timer shall be executed by the worker thread, but is in fact executed by the GUI thread. So, I deduce that the connection created by the singleShot timer is a DirectConnection instead of a QueuedConnection. That would explain why the slot is executed by the emitting thread.

Here is how I create the worker thread, and how I create the singleShot() timer


// code from the GUI thread
otherThread = new OtherThread(this);
otherThread->start();
QTimer::singleShot(0, otherThread, SLOT(startThread(void)));
cout << "In GUI Thread, Thread=" << currentThread() << " ThreadID=" << currentThreadId() << endl;
QCoreApplication::processEvents(); // process the timer event


here is my startThread() function (from the worker thread)


void OtherThread::startThread(void) {
cout << "In startThread() Thread=" << currentThread() << " ThreadID=" << currentThreadId() << endl;
// do some job...
}

outputs where we can see that the GUI thread executes the slot instead of the worker thread:
In GUI Thread, Thread=0x804f198 ThreadID=3078543040
In startThread() Thread=0x804f198 ThreadID=3078543040

Where is my mistake ? should QTimer::singleShot()'s receiver be something else that my otherThread ?

0xBulbizarre
20th March 2006, 13:18
I saw in the docs that:


By default, QObject::connect() establishes a direct connection if the sender and the receiver live in the same thread, and a queued connection if they live in different threads.


If we look at my code above (reproduced below)... we can see that both the sender and the receiver live in the same thread. The sender is the singleShot timer, the receiver is a thread object, also living in the GUI thread.


otherThread = new OtherThread(this);
otherThread->start();
QTimer::singleShot(0, otherThread, SLOT(startThread(void)));


is it correct ? how to solve that problem ?

jacek
20th March 2006, 15:47
QThread object lives in the thread that created it. You could split that class into two --- the thread itself and an object that lives within that thread.

0xBulbizarre
20th March 2006, 16:02
Ok, I now understand why a QThread should'nt do any business logic directly. I'll try that tonight!

thanks for what I learned in this (forum) Thread ;) !

madrich
9th April 2006, 21:55
Try this

otherThread = new OtherThread(this);
otherThread->moveToThread(otherThread);
otherThread->start();

that should work ... Regards Madrich