PDA

View Full Version : Waiting for a thread to finish without blocking the main event loop



bmn
22nd June 2012, 12:56
The following code is called by a subclass of QThread in the cancel method. I need to wait for the thread to finish, but I can't use QThread::wait() because I must not block the main UI thread. Following the advice here (http://doc.qt.nokia.com/qq/qq27-responsive-guis.html), I tried to use a QEventLoop.

QEventLoop eventLoop;
connect(this, SIGNAL(finished()), &eventLoop, SLOT(quit()));
// Race condition
if (!isFinished())
eventLoop.exec();
The problem is that the thread might emit the finished() signal after the call to isFinished(), but before the call to exec(). Unfortunately, exec() will not exit in this case. How can I prevent the dead lock if I don't want to add a timeout?

amleto
22nd June 2012, 13:46
"The following code is called by a subclass of QThread in the cancel method."

why would you be making an event loop in a cancel method?

bmn
22nd June 2012, 14:03
Because I want to wait for the thread to finish. I set a flag in cancel() which is checked by the thread's main while-loop in the run() method.

yeye_olive
22nd June 2012, 14:07
First of all, do you really have to wait for the thread to terminate in a local event loop? Can't you simply connect the QThread's finished() signal to some slot and let the main event loop deal with it? (Asynchronous-style programming vs your current synchronous style.)

To answer your question, there is no race condition here. Indeed, the QThread object lives in the main thread and its finished() signal will only have a chance of being emitted when control returns to an event loop, i.e. during the QEventLoop's exec().

bmn
22nd June 2012, 14:59
Yes, I really need to wait. This happens during shutdown, and I need to keep certain objects alive until the thread is finished.

Thank you for your answer. You're right, there is no race condition.

QEventLoop eventLoop;
connect(this, SIGNAL(finished()), &eventLoop, SLOT(quit()));
// Note: One might think that there is a race condition where finished() is emitted after
// the call to isFinished(), but before exec(). However, the finished() signal is emitted from
// the QThread, so if the current call lives in another thread (e. g. the main UI thread), the
// Qt::AutoConnection behaves like a Qt::QueuedConnection, and the slot is handled in the
// receiver's thread, i. e. in eventLoop.exec().
if (!isFinished())
eventLoop.exec();

yeye_olive
22nd June 2012, 17:40
I may have written something wrong above: there is no guarantee that QThread::finished() will be emitted from the main thread; it may well be emitted from the thread controlled by the QThread instance (and, looking at Qt's source, it indeed seems to be the case, at least on some platforms). But then, as you stated in your comment, the auto connection to QEventLoop::exec() behaves as a queued one. In both cases the signal is handled in the main thread, during QEventLoop::exec(). No race condition.

By the way, I usually call QThread::wait() even after having received finished(). I could not find anywhere a confirmation that all resources were guaranteed to be freed without it.

As for my suggestion of using the main event loop instead of a local one, I remembered solving the exact same problem you have by reimplementing the closeEvent() method of the main window of the GUI to prevent if from closing, asking the thread to exit instead. Only upon receiving QThread::finished() would I close the window. In the meantime the GUI would still respond.