QT Eventloop 和插槽句柄?
QT Eventloop and slot handle?
我创建了一个演示来研究 QObject、QThread 和 QT Signal/Slot 如下链接
想法是:
我创建了一个扩展自 QThread 的 ExtentQThread 并实现 运行() 函数,该函数将在调用 exec() 之前循环 loopTimeoutMsec(在构造函数上设置)(这使其进入线程事件循环)。我从 main 创建了一个 ExtentQThread extQThread1 实例,loopTimeoutMsec 设置为 10000.
然后我从主线程创建了两个 ExtentQObject 实例。 extQObject10 已创建并移动到 extQThread1 和未移动的 extQObject11。
测试期望:
- 主线程上的 extQObject11 插槽 运行 大约(超时 = 5000):通过
- extQObject10 插槽 运行 在 extQThread1 上:通过
- extQObject10 slot 运行 on extQThread1 at around (loopTimeoutMsec = 10000): NOT PASSED
[main.cpp]
#include <QCoreApplication>
#include <QTimer>
#include "extentqthread.h"
long baseMSecsSinceEpoch;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
baseMSecsSinceEpoch = QDateTime::currentMSecsSinceEpoch();
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "Main thread 1" << QThread::currentThreadId();
// === test1
ExtentQThread extQThread1("extQThread1 created from main thread", 10000);
ExtentQObject extQObject10("extQObject10 created from main thread then moved to extQThread1");
extQObject10.moveToThread(&extQThread1);
ExtentQObject extQObject11("extQObject11 created from main thread");
extQThread1.start();
// 1.0 to test signal of extQObject10 which is moved to extQThread1
// and signal of extQObject11 which is not moved
long timeout = 5000;
QTimer::singleShot(timeout, [&extQThread1, &timeout]() {
qDebug() << "\n==" << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "timeout" << timeout
<< "\n>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved"
<< "\n>> extQThread1.isRunning()" << extQThread1.isRunning();
});
QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
QTimer::singleShot(timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "Main thread 2" << QThread::currentThreadId();
return a.exec();
}
[extentqthread.cpp]
#include "extentqthread.h"
extern long baseMSecsSinceEpoch;
ExtentQThread::ExtentQThread(QString name_, long loopTimeoutMsec_)
{
name = name_;
loopTimeoutMsec = loopTimeoutMsec_;
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "loopTimeoutMsec" << loopTimeoutMsec
<< "created on thread" << QThread::currentThreadId();
}
void ExtentQThread::run() {
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "STARTED on thread" << QThread::currentThreadId();
ExtentQObject extQObject("extQObject created from (" + name + ")");
connect(this, &ExtentQThread::runFirstSlot, &extQObject, &ExtentQObject::onExtentQObjectFirstSlot);
if (loopTimeoutMsec < 0) {
while(1) {};
} else {
QThread::msleep(loopTimeoutMsec);
}
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "before exec() on thread" << QThread::currentThreadId();
exec();
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "after exec() on thread" << QThread::currentThreadId();
if (loopTimeoutMsec < 0) {
while(1) {};
} else {
QThread::msleep(loopTimeoutMsec);
}
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "STOPPED on thread" << QThread::currentThreadId();
}
void ExtentQThread::onExtentQThreadFirstSlot() {
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "run on thread" << QThread::currentThreadId();
Q_EMIT runFirstSlot();
}
[extentqobject.cpp]
#include "extentqobject.h"
extern long baseMSecsSinceEpoch;
ExtentQObject::ExtentQObject(QString name_)
{
name = name_;
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "created on thread" << QThread::currentThreadId();
}
void ExtentQObject::onExtentQObjectFirstSlot() {
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "run on thread" << QThread::currentThreadId();
}
这是输出
0 Main thread 1 0x7fdc8f3f3740
1 ExtentQThread::ExtentQThread(QString, long int) instance "extQThread1 created from main thread" loopTimeoutMsec 10000 created on thread 0x7fdc8f3f3740
1 ExtentQObject::ExtentQObject(QString) instance "extQObject10 created from main thread then moved to extQThread1" created on thread 0x7fdc8f3f3740
1 ExtentQObject::ExtentQObject(QString) instance "extQObject11 created from main thread" created on thread 0x7fdc8f3f3740
1 Main thread 2 0x7fdc8f3f3740
1 virtual void ExtentQThread::run() instance "extQThread1 created from main thread" STARTED on thread 0x7fdc8aa03700
1 ExtentQObject::ExtentQObject(QString) instance "extQObject created from (extQThread1 created from main thread)" created on thread 0x7fdc8aa03700
== 4754 timeout 5000
>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved
>> extQThread1.isRunning() true
4754 void ExtentQObject::onExtentQObjectFirstSlot() instance "extQObject11 created from main thread" run on thread 0x7fdc8f3f3740
10001 virtual void ExtentQThread::run() instance "extQThread1 created from main thread" before exec() on thread 0x7fdc8aa03700
14756 void ExtentQObject::onExtentQObjectFirstSlot() instance "extQObject10 created from main thread then moved to extQThread1" run on thread 0x7fdc8aa03700
据我了解,我希望:
ExtentQObject::onExtentQObjectFirstSlot() 实例 "extQObject10 created from main thread then moved to extQThread1" 运行 在线程 0x7fdc8aa03700 运行 上大约 10000(毫秒)而不是 14756(毫秒)。因为信号是在 5000(msec) 发出的,进入 ExtentQthread 的 exec() 在 10000(msec) 之后是 运行,它应该处理 onExtentQObjectFirstSlot 然后。
谁能解释一下?
////
- 我试图从 QTimer::singleShot 更改为 QTimer 实例,它给出了预期的行为(差异如下)
diff --git a/main.cpp b/main.cpp
index ed45d23..0ebabf3 100644
--- a/main.cpp
+++ b/main.cpp
@@ -25,14 +25,17 @@ int main(int argc, char *argv[])
// 1.0 to test signal of extQObject10 which is moved to extQThread1
// and signal of extQObject11 which is not moved
+ QTimer timer;
+ timer.setSingleShot(true);
long timeout = 5000;
- QTimer::singleShot(timeout, [&extQThread1, &timeout]() {
+ QObject::connect(&timer, &QTimer::timeout, [&extQThread1, &timeout]() {
qDebug() << "\n==" << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "timeout" << timeout
<< "\n>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved"
<< "\n>> extQThread1.isRunning()" << extQThread1.isRunning();
});
- QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
- QTimer::singleShot(timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);
+ QObject::connect(&timer, &QTimer::timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
+ QObject::connect(&timer, &QTimer::timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);
+ timer.start(timeout);
我不确定你想做什么,但有一些建议:
- 大多数时候,您不必覆盖
run
方法,除非您需要非常密切地控制线程 activity。只需创建一个 QThread,使用 moveToThread
并启动线程,就是这样。然后,您可以使用线程 started
信号或其他 signal/slot 连接来确保它在您的线程中执行
exec
将有效地 运行 线程事件循环,并且 return 仅当线程存在时才有效。
- 您可以使用
exec
和quit
方法手动run/stop事件循环
- 当
sleep
ing 时,或者更一般地说,当事件循环不是 运行ing 时,不会处理任何事件或执行插槽。因此,要执行使用 Invoke 调用的槽或通过来自另一个线程的信号,必须在消息在 QThread 事件循环中排队后调用 exec
。
更一般地说,始终使用 signals/slots(你所做的)在适当的线程中执行代码,并在你可以在更高级别 类 时依赖,如 QtConcurrent
。
更多详情,你可以看看Qt documentation, as well as this (a bit older) blog article
我找到了原因,可能这对某人有帮助
QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
在这种情况下 QTimer::singleShot 调用这个重载函数
QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj)
: QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(r), receiver(r), slotObj(slotObj)
{
timerId = startTimer(msec, timerType);
if (r && thread() != r->thread()) {
// Avoid leaking the QSingleShotTimer instance in case the application exits before the timer fires
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
setParent(0);
moveToThread(r->thread());
}
}
这创建了一个定时器实例(让我们调用 timerA)并且因为 extQObject10,它是为 QTimer::singleShot 设置的接收器,被移动到 extThread1 所以 timerA 也被移到了那里。
因为 QTimer 扩展了 QObject 所以它从 QObject 继承了 bool QObject::event(QEvent *e) 处理线程变化如下
case QEvent::ThreadChange: {
Q_D(QObject);
QThreadData *threadData = d->threadData;
QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.load();
if (eventDispatcher) {
QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);
if (!timers.isEmpty()) {
// do not to release our timer ids back to the pool (since the timer ids are moving to a new thread).
eventDispatcher->unregisterTimers(this);
QMetaObject::invokeMethod(this, "_q_reregisterTimers", Qt::QueuedConnection,
Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers))));
}
}
break;
这里,QMetaObject::invokeMethod 由 extQThread1 产生一个插槽句柄,所以它将在 之后处理loopTimeoutMsec 设置为 extQThread1。之后,timerA 启动 运行 并在为其设置的 timeout 后触发,届时 onExtentQObjectFirstSlot 将在 extObject10.
总而言之,onExtentQObjectFirstSlot 将在 loopTimeoutMsec 之后在 extObject10 上调用(为 extQThread1 设置) + 超时(为QTimer::singleShot设置)
我创建了一个演示来研究 QObject、QThread 和 QT Signal/Slot 如下链接
想法是:
我创建了一个扩展自 QThread 的 ExtentQThread 并实现 运行() 函数,该函数将在调用 exec() 之前循环 loopTimeoutMsec(在构造函数上设置)(这使其进入线程事件循环)。我从 main 创建了一个 ExtentQThread extQThread1 实例,loopTimeoutMsec 设置为 10000.
然后我从主线程创建了两个 ExtentQObject 实例。 extQObject10 已创建并移动到 extQThread1 和未移动的 extQObject11。
测试期望:
- 主线程上的 extQObject11 插槽 运行 大约(超时 = 5000):通过
- extQObject10 插槽 运行 在 extQThread1 上:通过
- extQObject10 slot 运行 on extQThread1 at around (loopTimeoutMsec = 10000): NOT PASSED
[main.cpp]
#include <QCoreApplication>
#include <QTimer>
#include "extentqthread.h"
long baseMSecsSinceEpoch;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
baseMSecsSinceEpoch = QDateTime::currentMSecsSinceEpoch();
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "Main thread 1" << QThread::currentThreadId();
// === test1
ExtentQThread extQThread1("extQThread1 created from main thread", 10000);
ExtentQObject extQObject10("extQObject10 created from main thread then moved to extQThread1");
extQObject10.moveToThread(&extQThread1);
ExtentQObject extQObject11("extQObject11 created from main thread");
extQThread1.start();
// 1.0 to test signal of extQObject10 which is moved to extQThread1
// and signal of extQObject11 which is not moved
long timeout = 5000;
QTimer::singleShot(timeout, [&extQThread1, &timeout]() {
qDebug() << "\n==" << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "timeout" << timeout
<< "\n>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved"
<< "\n>> extQThread1.isRunning()" << extQThread1.isRunning();
});
QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
QTimer::singleShot(timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "Main thread 2" << QThread::currentThreadId();
return a.exec();
}
[extentqthread.cpp]
#include "extentqthread.h"
extern long baseMSecsSinceEpoch;
ExtentQThread::ExtentQThread(QString name_, long loopTimeoutMsec_)
{
name = name_;
loopTimeoutMsec = loopTimeoutMsec_;
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "loopTimeoutMsec" << loopTimeoutMsec
<< "created on thread" << QThread::currentThreadId();
}
void ExtentQThread::run() {
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "STARTED on thread" << QThread::currentThreadId();
ExtentQObject extQObject("extQObject created from (" + name + ")");
connect(this, &ExtentQThread::runFirstSlot, &extQObject, &ExtentQObject::onExtentQObjectFirstSlot);
if (loopTimeoutMsec < 0) {
while(1) {};
} else {
QThread::msleep(loopTimeoutMsec);
}
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "before exec() on thread" << QThread::currentThreadId();
exec();
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "after exec() on thread" << QThread::currentThreadId();
if (loopTimeoutMsec < 0) {
while(1) {};
} else {
QThread::msleep(loopTimeoutMsec);
}
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "STOPPED on thread" << QThread::currentThreadId();
}
void ExtentQThread::onExtentQThreadFirstSlot() {
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "run on thread" << QThread::currentThreadId();
Q_EMIT runFirstSlot();
}
[extentqobject.cpp]
#include "extentqobject.h"
extern long baseMSecsSinceEpoch;
ExtentQObject::ExtentQObject(QString name_)
{
name = name_;
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "created on thread" << QThread::currentThreadId();
}
void ExtentQObject::onExtentQObjectFirstSlot() {
qDebug() << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << Q_FUNC_INFO
<< "instance" << name
<< "run on thread" << QThread::currentThreadId();
}
这是输出
0 Main thread 1 0x7fdc8f3f3740
1 ExtentQThread::ExtentQThread(QString, long int) instance "extQThread1 created from main thread" loopTimeoutMsec 10000 created on thread 0x7fdc8f3f3740
1 ExtentQObject::ExtentQObject(QString) instance "extQObject10 created from main thread then moved to extQThread1" created on thread 0x7fdc8f3f3740
1 ExtentQObject::ExtentQObject(QString) instance "extQObject11 created from main thread" created on thread 0x7fdc8f3f3740
1 Main thread 2 0x7fdc8f3f3740
1 virtual void ExtentQThread::run() instance "extQThread1 created from main thread" STARTED on thread 0x7fdc8aa03700
1 ExtentQObject::ExtentQObject(QString) instance "extQObject created from (extQThread1 created from main thread)" created on thread 0x7fdc8aa03700
== 4754 timeout 5000
>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved
>> extQThread1.isRunning() true
4754 void ExtentQObject::onExtentQObjectFirstSlot() instance "extQObject11 created from main thread" run on thread 0x7fdc8f3f3740
10001 virtual void ExtentQThread::run() instance "extQThread1 created from main thread" before exec() on thread 0x7fdc8aa03700
14756 void ExtentQObject::onExtentQObjectFirstSlot() instance "extQObject10 created from main thread then moved to extQThread1" run on thread 0x7fdc8aa03700
据我了解,我希望: ExtentQObject::onExtentQObjectFirstSlot() 实例 "extQObject10 created from main thread then moved to extQThread1" 运行 在线程 0x7fdc8aa03700 运行 上大约 10000(毫秒)而不是 14756(毫秒)。因为信号是在 5000(msec) 发出的,进入 ExtentQthread 的 exec() 在 10000(msec) 之后是 运行,它应该处理 onExtentQObjectFirstSlot 然后。
谁能解释一下?
////
- 我试图从 QTimer::singleShot 更改为 QTimer 实例,它给出了预期的行为(差异如下)
diff --git a/main.cpp b/main.cpp index ed45d23..0ebabf3 100644 --- a/main.cpp +++ b/main.cpp @@ -25,14 +25,17 @@ int main(int argc, char *argv[])
// 1.0 to test signal of extQObject10 which is moved to extQThread1
// and signal of extQObject11 which is not moved
+ QTimer timer;
+ timer.setSingleShot(true);
long timeout = 5000;
- QTimer::singleShot(timeout, [&extQThread1, &timeout]() {
+ QObject::connect(&timer, &QTimer::timeout, [&extQThread1, &timeout]() {
qDebug() << "\n==" << QDateTime::currentMSecsSinceEpoch() - baseMSecsSinceEpoch << "timeout" << timeout
<< "\n>> To test signal of extQObject10 which is moved to extQThread1 and signal of extQObject11 which is not moved"
<< "\n>> extQThread1.isRunning()" << extQThread1.isRunning();
});
- QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
- QTimer::singleShot(timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);
+ QObject::connect(&timer, &QTimer::timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
+ QObject::connect(&timer, &QTimer::timeout, &extQObject11, &ExtentQObject::onExtentQObjectFirstSlot);
+ timer.start(timeout);
我不确定你想做什么,但有一些建议:
- 大多数时候,您不必覆盖
run
方法,除非您需要非常密切地控制线程 activity。只需创建一个 QThread,使用moveToThread
并启动线程,就是这样。然后,您可以使用线程started
信号或其他 signal/slot 连接来确保它在您的线程中执行 exec
将有效地 运行 线程事件循环,并且 return 仅当线程存在时才有效。- 您可以使用
exec
和quit
方法手动run/stop事件循环 - 当
sleep
ing 时,或者更一般地说,当事件循环不是 运行ing 时,不会处理任何事件或执行插槽。因此,要执行使用 Invoke 调用的槽或通过来自另一个线程的信号,必须在消息在 QThread 事件循环中排队后调用exec
。
更一般地说,始终使用 signals/slots(你所做的)在适当的线程中执行代码,并在你可以在更高级别 类 时依赖,如 QtConcurrent
。
更多详情,你可以看看Qt documentation, as well as this (a bit older) blog article
我找到了原因,可能这对某人有帮助
QTimer::singleShot(timeout, &extQObject10, &ExtentQObject::onExtentQObjectFirstSlot);
在这种情况下 QTimer::singleShot 调用这个重载函数
QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj)
: QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(r), receiver(r), slotObj(slotObj)
{
timerId = startTimer(msec, timerType);
if (r && thread() != r->thread()) {
// Avoid leaking the QSingleShotTimer instance in case the application exits before the timer fires
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
setParent(0);
moveToThread(r->thread());
}
}
这创建了一个定时器实例(让我们调用 timerA)并且因为 extQObject10,它是为 QTimer::singleShot 设置的接收器,被移动到 extThread1 所以 timerA 也被移到了那里。
因为 QTimer 扩展了 QObject 所以它从 QObject 继承了 bool QObject::event(QEvent *e) 处理线程变化如下
case QEvent::ThreadChange: {
Q_D(QObject);
QThreadData *threadData = d->threadData;
QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.load();
if (eventDispatcher) {
QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);
if (!timers.isEmpty()) {
// do not to release our timer ids back to the pool (since the timer ids are moving to a new thread).
eventDispatcher->unregisterTimers(this);
QMetaObject::invokeMethod(this, "_q_reregisterTimers", Qt::QueuedConnection,
Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers))));
}
}
break;
这里,QMetaObject::invokeMethod 由 extQThread1 产生一个插槽句柄,所以它将在 之后处理loopTimeoutMsec 设置为 extQThread1。之后,timerA 启动 运行 并在为其设置的 timeout 后触发,届时 onExtentQObjectFirstSlot 将在 extObject10.
总而言之,onExtentQObjectFirstSlot 将在 loopTimeoutMsec 之后在 extObject10 上调用(为 extQThread1 设置) + 超时(为QTimer::singleShot设置)