简体   繁体   中英

What happens with Qt::BlockingQueuedConnection emission if target object dies?

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)?

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 returns false only when there is a problem in the parameters passed to it (eg 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 a QSemaphore . The QSemaphore is stored into the QMetaCallEvent that is posted to the receiver object.
  • This QSemaphore is released when the QMetaCallEvent is destroyed.
  • QObject 's destructor is responsible for calling QCoreApplication::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, because invokeMethod 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 whole invokeMethod call (and hence avoiding this whole question).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM