When I send a method call using invokeMethod
, what happens when the sending code waits on the call, but the target object dies subsequently? Will this end in an infinite wait? Or will Qt wake up the caller and return false
(which would be an undocumented behavior, and a best guess by myself)?
Asked
Active
Viewed 1,977 times
3

Johannes Schaub - litb
- 496,577
- 130
- 894
- 1,212
1 Answers
0
The following example deletes the worker object while invokeMethod
is waiting for a BlockingQueuedConnection
:
#include <QtCore>
//a thread that can be destroyed at any time
//see http://stackoverflow.com/a/25230470
class SafeThread : public QThread{
using QThread::run;
public:
explicit SafeThread(QObject* parent= nullptr):QThread(parent){}
~SafeThread(){ quit(); wait(); }
};
//The function queues a functor to get executed in a specified worker's thread
template <typename Func>
void PostToThread(QThread* thread, Func&& f) {
//see http://stackoverflow.com/a/21653558
QObject temporaryObject;
QObject::connect(&temporaryObject, &QObject::destroyed,
thread->eventDispatcher(), std::forward<Func>(f),
Qt::QueuedConnection);
}
//a typical QObject worker that can "printName"
class Worker : public QObject {
Q_OBJECT
public:
using QObject::QObject;
~Worker() {
qInfo() << "destroying " << objectName()
<< " in " << QThread::currentThread()->objectName();
}
Q_SLOT void printName() {
qInfo() << "my name is " << objectName()
<< " in " << QThread::currentThread()->objectName();
}
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
//create worker
Worker *worker = new Worker;
worker->setObjectName("worker");
//start worker thread and move worker to it
SafeThread t;
worker->moveToThread(&t);
t.start();
//set thread names (for better output)
QThread::currentThread()->setObjectName("main_thread");
t.setObjectName("worker_thread");
//normal QMetaObject::invokeMethod usage
if(QMetaObject::invokeMethod(worker, "printName",
Qt::BlockingQueuedConnection)) {
qInfo() << "printName called successfully before deletion";
}
//the lambda function will be executed in the worker thread
PostToThread(&t, [worker]{
qInfo() << "blocking " << QThread::currentThread()->objectName();
QThread::sleep(2); //block worker thread for 2 seconds
delete worker; //delete worker
});
//at this point the worker thread is about to destroy the worker object (but
//hasn't done so yet)
if(QMetaObject::invokeMethod(worker, "printName",
Qt::BlockingQueuedConnection)) {
qInfo() << "printName called successfully after deletion!";
}
QTimer::singleShot(100, &a, &QCoreApplication::quit);
return a.exec();
}
#include "main.moc"
Output (tested on Qt 5.9.1, Qt 5.7 - windows, debian):
my name is "worker" in "worker_thread"
printName called successfully before deletion
blocking "worker_thread"
destroying "worker" in "worker_thread"
printName called successfully after deletion!
So a short answer is: invokeMethod
returns true
but nothing gets called. However, please note that you have to guarantee that the worker object is still valid at the beginning of (see last point for more details) the invokeMethod
call the main thread (otherwise, it is UB).
Here is a list of conclusions that I got into by digging through Qt's code:
ivokeMethod
returnsfalse
only when there is a problem in the parameters passed to it (e.g. slot signature does not match parameters count/type, return type mismatch, unknown connection type, ...). See here.- When using
Qt::BlockingQueuedConnection
,invokeMethod
blocks the calling thread by acquiring aQSemaphore
. TheQSemaphore
is stored into theQMetaCallEvent
that is posted to the receiver object. - This
QSemaphore
is released when theQMetaCallEvent
is destroyed. QObject
's destructor is responsible for callingQCoreApplication::removePostedEvents()
for the object being destructed. This means that all the events in the event queue that are targeted to an object are destroyed upon this object's destruction. See here.- You need to make sure that the worker object stays alive while the calling thread executes
invokeMethod
until the mentioned semaphore is acquired, becauseinvokeMethod
might try to access the worker object at any point. I think that this requirement can make things complicated in practice, as one might end up having to guarantee the lifetime of the object throughout the wholeinvokeMethod
call (and hence avoiding this whole question).

Mike
- 8,055
- 1
- 30
- 44