gpt4 book ai didi

qt - QMetaObject::invokeMethod 在...时不起作用

转载 作者:行者123 更新时间:2023-12-04 12:54:15 29 4
gpt4 key购买 nike

...从静态类和非主线程调用。
简而言之,我有一个类“sapp”,它有另一个静态类“tobj”作为静态成员。为了避免静态订单初始化失败,tobj 在 sapp 的方法中声明,该方法又返回 tobj 实例的指针。
我的问题是, tobj 有一个应该在构造函数中启动的计时器,并且 tobj 可能由非主线程创建。 QTimer 不能由主线程以外的线程启动(或者我猜是没有事件循环的线程)。
出于这个原因,我通过 QMetaObject::invokeMethod + Qt::QueuedConnection 调用 QTimer::start 以避免线程问题,但是它不起作用,QTimer::start 永远不会被调用。我调查了一下这个问题,看起来,QTimer::start 没有被调用,因为 QTimer 的父级(在这种情况下为 tobj)被声明为静态。如果我将 tobj 声明为非静态成员,则一切正常。

我不太了解 Qt 的内部结构,这可能是一个错误还是我做错了什么?

这是代码:

class tobj : public QObject
{
Q_OBJECT

QTimer timer;
private slots:
void timeout();

public:
tobj();
};

class sapp : public QObject
{
Q_OBJECT

public:
static tobj* f();
};


void tobj::timeout()
{
qDebug() << "hi";
}

tobj::tobj()
{
connect(&timer, SIGNAL(timeout()), this, SLOT(timeout()));
timer.setInterval(500);
qDebug() << QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection); // returns true, but never invoked.
}

tobj* sapp::f()
{
static tobj ff;
return &ff;
}

这是测试项目的链接,由1个头文件和1个cpp文件 http://dl.dropbox.com/u/3055964/untitled.zip组成

我正在 Qt 4.8.0 和 MSVC 2010 上进行测试。

非常感谢,非常感谢您的帮助。

最佳答案

我觉得你做得过火了。 Qt 的美妙之处在于您尝试做的事情非常容易。

您似乎认为 QTimer会以某种方式神奇地中断您正在运行的线程以执行回调。在 Qt 中从来没有这种情况。在 Qt 中,所有事件和信号都被传递到线程中运行的事件循环,并且该事件循环将它们分派(dispatch)给 QObjects。因此,在一个线程中,由于 Qt 的事件和信号/槽框架,不存在并发风险。此外,只要您依赖 Qt 的信号槽和事件机制在线程之间进行通信,您就不需要使用任何其他访问控制原语,因为每个线程中的东西都是序列化的。

所以,您的问题是您从未在线程中运行事件循环,因此永远不会拾取超时事件并将其分派(dispatch)到您已连接到 timeout() 的插槽。信号。

一个 QTimer可以在任何线程中创建和启动,只要你启动它的线程是定时器的QObject QObjects 属于你创建它们的线程,除非你使用 QObject::moveToThread(QThread*) 将它们移动到不同的线程。 .

使用invokeMethod启动计时器是完全没有必要的。毕竟,你是从它所在的同一个线程启动计时器。顾名思义,排队的信号槽连接会将信号排入队列。具体来说,当您发出信号时,QMetaCallEvent为接收器插槽所在的 QObject 排队。必须在插槽对象的线程中运行一个事件循环来获取它并执行调用。你永远不会在线程中调用任何东西来清空该队列,因此没有什么可以调用你的 timeout()投币口。

至于静态成员初始化惨败,你可以自由地在 main() 中构建你的整个 T 对象。或者在主线程中的 QObject 中,然后将其移动到新线程——这就是不使用 QtConcurrent 时的做法。使用 QtConcurrent 时,您的可运行函数可以构造和破坏任意数量的 QObject。

要修复您的代码,lambda 应该旋转一个事件循环,因此:

[] () {
sapp s;
s.f();
QEventLoop l;
l.exec();
}

下面我附上一个 SSCCE 示例,说明如何在 Qt 中惯用地完成它。如果不想使用 QtConcurrent,那么会有两个变化:你想要 qApp->exit()在你之后 exit()线程的事件循环——否则, a.exec()永远不会退出(它怎么知道?)。您还想 wait()在退出 main() 之前的线程上功能 - 破坏 QThreads 是不好的风格仍在运行。
exit()函数仅表示事件循环结束,将它们视为设置标志,而不是真正自行退出任何东西——因此它们不同于 C 风格的 API exit()s .

请注意, QTimerQObject在其本身。出于性能原因,如果计时器经常触发,使用 QBasicTimer 会便宜得多。这是对 QObject::startTimer() 返回的计时器 id 的简单包装。 .然后重新实现 QObject::timerEvent() .这样可以避免信号槽调用的开销。根据经验,直接(非排队)信号槽调用的成本大约相当于将两个 1000 个字符的 QString 连接在一起的成本。见 my benchmark .

旁注:如果您要发布简短的示例,直接发布整个代码可能会更容易,这样它就不会在 future 丢失——只要它们都在一个文件中。将 100 行示例代码散布在三个文件中会适得其反。见下文,关键是 #include "filename.moc"filename.cpp 结尾.它还有助于一次定义和声明方法,Java 风格。一切都是为了保持简短易懂。毕竟,这是一个例子。
//main.cpp
#include <QtCore/QTimer>
#include <QtCore/QDebug>
#include <QtCore>
#include <QtCore/QCoreApplication>

class Class : public QObject
{
Q_OBJECT
QTimer timer;
int n;
private slots:
void timeout() {
qDebug() << "hi";
if (! --n) {
QThread::currentThread()->exit();
}
}
public:
Class() : n(5) {
connect(&timer, SIGNAL(timeout()), SLOT(timeout()));
timer.start(500);
}
};

void fun()
{
Class c;
QEventLoop loop;
loop.exec();
qApp->exit();
}

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QtConcurrent::run(&fun);
return a.exec();
}

#include "main.moc"

关于qt - QMetaObject::invokeMethod 在...时不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10356343/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com