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

时间:2012-04-27 18:57:49

标签: qt static-members

...从静态类和非主线程调用。 简而言之,我有一个类“sapp”,它有另一个静态类“tobj”作为静态成员。为了避免静态顺序初始化失败,在sapp的方法中声明了tobj,而该方法又返回了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。

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

1 个答案:

答案 0 :(得分:4)

我认为你过度了。 Qt的美丽在于你尝试做的事情很容易。

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

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

可以在任何线程中创建并启动QTimer,只要启动它的线程是计时器QObject所在的线程.QObjects属于您创建它们的线程。 ,除非使用QObject::moveToThread(QThread*)将它们移动到其他线程。

使用invokeMethod启动计时器是完全没必要的。毕竟,你是从它所在的同一个线程开始计时器。如名称所示,排队的信号槽连接将队列中的信号排队。具体来说,当您发出信号时,QMetaCallEvent排队等待接收器插槽所在的QObject。事件循环必须在slot对象的线程中运行才能选择它并执行调用。你永远不会在线程中调用任何东西来清空那个队列,因此没有什么可以调用你的timeout()插槽。

对于静态成员初始化fiasco,你可以在main()中或在主线程中的QObject中构建整个T对象,然后将其移动到新线程 - 这就是如何如果不使用QtConcurrent,就会做到这一点。使用QtConcurrent时,runnable函数可以构造和销毁任意数量的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不同。 / p>

请注意,QTimer本身就是QObject。出于性能原因,如果计时器经常触发,使用QBasicTimer便宜得多,QObject::startTimer()QObject::timerEvent()返回的计时器ID的简单包装器。然后,您将重新实现#include "filename.moc"。这样就可以避免信号槽调用的开销。根据经验,直接(非排队)信号槽调用的成本与将两个1000个字符的QStrings连接在一起的成本大约相同。请参阅my benchmark

附注:如果您发布简短的示例,可能更容易直接发布整个代码,以便将来不会丢失 - 只要它们都在一个文件中。在三个文件中散布100行示例代码会适得其反。请参阅下文,关键是filename.cpp末尾的//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" 。它还有助于一次性定义和声明方法,Java风格。所有这些都是为了让它简短易懂。毕竟这是一个例子。

{{1}}