如果目标对象死亡,Qt::BlockingQueuedConnection 发射会发生啥?

Posted

技术标签:

【中文标题】如果目标对象死亡,Qt::BlockingQueuedConnection 发射会发生啥?【英文标题】:What happens with Qt::BlockingQueuedConnection emission if target object dies?如果目标对象死亡,Qt::BlockingQueuedConnection 发射会发生什么? 【发布时间】:2018-07-20 16:39:57 【问题描述】:

当我使用invokeMethod 发送方法调用时,如果发送代码等待调用,但目标对象随后死亡,会发生什么情况?这会以无限的等待结束吗?或者 Qt 会唤醒调用者并返回 false(这将是一种未记录的行为,我自己最好的猜测)?

【问题讨论】:

【参考方案1】:

以下示例在invokeMethod 等待BlockingQueuedConnection 时删除工作对象:

#include <QtCore>

//a thread that can be destroyed at any time
//see http://***.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://***.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"

输出(在 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!

所以一个简短的答案是:invokeMethod 返回true,但没有被调用。但是,请注意,您必须保证worker对象在开头仍然有效(更多详细信息请参见最后一点)invokeMethod调用主线程(否则为UB) .

这是我通过挖掘 Qt 的代码得出的一系列结论:

ivokeMethod 仅在传递给它的参数有问题时才返回 false(例如,槽签名与参数计数/类型不匹配、返回类型不匹配、未知连接类型……)。见here。 当使用Qt::BlockingQueuedConnection时,invokeMethod会被acquiring a QSemaphore阻塞调用线程。 QSemaphore 存储在QMetaCallEvent 中,该QMetaCallEvent 发布到接收器对象。 这个QSemaphoreis releasedQMetaCallEvent被销毁时。 QObject 的析构函数负责调用QCoreApplication::removePostedEvents() 来为被析构的对象。这意味着事件队列中针对一个对象的所有事件都会在该对象被销毁时被销毁。见here。 您需要确保工作对象在调用线程执行invokeMethod 时保持活动状态,直到获得提到的信号量,因为invokeMethod 可能会在任何时候尝试访问工作对象。我认为这一要求在实践中会使事情变得复杂,因为最终可能不得不在整个 invokeMethod 调用中保证对象的生命周期(从而避免整个问题)。

【讨论】:

以上是关于如果目标对象死亡,Qt::BlockingQueuedConnection 发射会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章

Java对象"后事处理"那点事儿——垃圾回收

实验二:多线程

十根搜索算法

JVM 判断对象已死亡?

jvm-对象生存还是死亡

死亡时播放的 UI 对象动画