如果目标对象死亡,Qt::BlockingQueuedConnection emission 会发生什么?
What happens with Qt::BlockingQueuedConnection emission if target object dies?
当我使用 invokeMethod
发送方法调用时,如果发送代码等待调用,但目标对象随后死亡,会发生什么情况?这会在无限等待中结束吗?或者 Qt 会唤醒调用者和 return false
(这将是一种未记录的行为,并且是我自己的最佳猜测)?
以下示例在 invokeMethod
等待 BlockingQueuedConnection
时删除工作对象:
#include <QtCore>
//a thread that can be destroyed at any time
//see
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
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
returns true
但没有任何调用。但是,请注意,在 开始时,您必须保证 worker 对象仍然有效 (有关详细信息,请参阅最后一点) invokeMethod
调用主线程(否则, 是 UB).
以下是我通过挖掘 Qt 代码得出的结论列表:
ivokeMethod
returns false
仅当传递给它的参数有问题时(例如插槽签名与参数不匹配 count/type, return 类型不匹配,未知连接类型,...)。参见 here。
- 当使用
Qt::BlockingQueuedConnection
时,invokeMethod
通过 acquiring a QSemaphore
阻塞调用线程。 QSemaphore
存储到发布到接收方对象的 QMetaCallEvent
中。
- 这个
QSemaphore
is released当QMetaCallEvent
被销毁
QObject
的析构函数负责调用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.
- 您需要确保 worker 对象在调用线程执行
invokeMethod
时保持活动状态,直到获取提到的信号量,因为 invokeMethod
可能会在任何时候尝试访问 worker 对象。我认为这一要求在实践中会使事情变得复杂,因为最终可能不得不在整个 invokeMethod
调用期间保证对象的生命周期(因此避免了这整个问题)。
当我使用 invokeMethod
发送方法调用时,如果发送代码等待调用,但目标对象随后死亡,会发生什么情况?这会在无限等待中结束吗?或者 Qt 会唤醒调用者和 return false
(这将是一种未记录的行为,并且是我自己的最佳猜测)?
以下示例在 invokeMethod
等待 BlockingQueuedConnection
时删除工作对象:
#include <QtCore>
//a thread that can be destroyed at any time
//see
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
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
returns true
但没有任何调用。但是,请注意,在 开始时,您必须保证 worker 对象仍然有效 (有关详细信息,请参阅最后一点) invokeMethod
调用主线程(否则, 是 UB).
以下是我通过挖掘 Qt 代码得出的结论列表:
ivokeMethod
returnsfalse
仅当传递给它的参数有问题时(例如插槽签名与参数不匹配 count/type, return 类型不匹配,未知连接类型,...)。参见 here。- 当使用
Qt::BlockingQueuedConnection
时,invokeMethod
通过 acquiring aQSemaphore
阻塞调用线程。QSemaphore
存储到发布到接收方对象的QMetaCallEvent
中。 - 这个
QSemaphore
is released当QMetaCallEvent
被销毁 QObject
的析构函数负责调用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.- 您需要确保 worker 对象在调用线程执行
invokeMethod
时保持活动状态,直到获取提到的信号量,因为invokeMethod
可能会在任何时候尝试访问 worker 对象。我认为这一要求在实践中会使事情变得复杂,因为最终可能不得不在整个invokeMethod
调用期间保证对象的生命周期(因此避免了这整个问题)。