代码之家  ›  专栏  ›  技术社区  ›  Johannes Schaub - litb

如果目标对象死亡,qt::blockingqueuedconnection发射会发生什么?

  •  2
  • Johannes Schaub - litb  · 技术社区  · 7 年前

    当我使用 invokeMethod ,当发送代码等待调用,但目标对象随后死亡时会发生什么?这会以无限的等待而结束吗?或者qt会唤醒来电者并返回 false (哪一种行为是无证的,而我自己的最佳猜测)?

    1 回复  |  直到 7 年前
        1
  •  0
  •   Mike    7 年前

    下面的示例将在 invokeMethod 正在等待 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"
    

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

    所以简单的答案是: 调用方法 收益率 true 但什么都没有。但是,请注意,您必须保证worker对象仍然有效。 (详见最后一点) 调用方法 调用主线程(否则,它是ub)。

    下面是我通过挖掘qt代码得出的结论列表:

    • ivokeMethod 收益率 false 仅当传递给它的参数有问题时(例如槽签名与参数计数/类型不匹配、返回类型不匹配、未知连接类型…)。见 here .
    • 使用时 Qt::BlockingQueuedConnection , 调用方法 通过阻止调用线程 acquiring a QSemaphore . 这个 Q信号机 存储到 QMetaCallEvent 发布到接收对象。
    • 这个 Q信号机 is released QmetaCallEvent(详细事件) 被摧毁。
    • QObject 的析构函数负责调用 QCoreApplication::removePostedEvents() 对于被破坏的对象。这意味着事件队列中针对某个对象的所有事件在该对象被销毁时都会被销毁。见 here .
    • 您需要确保在调用线程执行时工作对象保持活动状态 调用方法 直到获得上述信号量,因为 调用方法 可能会尝试在任何点访问工作对象。我认为这一要求在实践中会使事情变得复杂,因为人们最终可能不得不保证整个对象的生命周期。 调用方法 打电话(因此避免了整个问题)。
    推荐文章