首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >…时,QMetaObject::invokeMethod不工作

…时,QMetaObject::invokeMethod不工作
EN

Stack Overflow用户
提问于 2012-04-28 02:57:49
回答 1查看 4.2K关注 0票数 4

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

我不太理解Qt的内部原理,这可能是一个bug,或者我做错了什么?

代码如下:

代码语言:javascript
复制
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;
}

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

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

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

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2012-06-04 20:21:29

我觉得你做得太过分了。Qt的美妙之处在于你尝试去做的事情非常简单。

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

因此,您的问题在于,您从未在线程中运行过事件循环,因此超时事件永远不会被提取并分派到您连接到timeout()信号的插槽。

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

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

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

要修复您的代码,lambda应该旋转一个事件循环,如下所示:

代码语言:javascript
复制
[] () {
    sapp s;
    s.f();
    QEventLoop l;
    l.exec();
}

下面我附上一个SSCCE示例,说明如何在Qt中地道地完成它。如果不想使用QtConcurrent,那么会有两个变化:您希望在退出线程的事件循环之后立即执行qApp->exit() --否则,a.exec()将永远不会退出(它怎么知道要退出?)。您还需要在退出main()函数之前在线程上执行wait() -销毁仍在运行的QThreads是一种糟糕的风格。

exit()函数只通知事件循环结束,将它们看作是设置一个标志,而不是自己退出任何东西--因此它们与C风格的exit()s不同。

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

附注:如果您发布的是简短的示例,那么直接发布整个代码可能更容易,这样将来就不会丢失代码--只要它们都在一个文件中。将100行示例代码分散在三个文件中是适得其反的。如下所示,关键是在filename.cpp的末尾执行#include "filename.moc"。它还有助于一次性定义和声明Java风格的所有方法。所有这些都是以保持简短和易于理解为名义的。毕竟,这只是一个例子。

代码语言:javascript
复制
//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"
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/10356343

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档