2012-04-27 134 views
3

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

我不太了解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

测试

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

回答

4

我觉得你太过分了。 Qt的美丽在于你试图做的事情非常简单。

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

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

一个QTimer可以创建在任何线程开始,只要你开始它的线程是线程计时器的QObject人死亡。QObject对象属于您在创建他们的线程,除非你将其移动到使用QObject::moveToThread(QThread*)的不同线程。

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

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

要解决你的代码,拉姆达应旋转事件循环,即:

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

下面我附上怎么会Qt中被惯用做了SSCCE例子。如果你不想使用QtConcurrent,那么会有两个变化:你想在exit()这个线程的事件循环后面qApp->exit() - 否则,a.exec()永远不会退出(它会怎么知道?)。在退出main()函数之前,您还希望在线程中使用wait() - 销毁仍在运行的QThreads的风格很糟糕。

exit()函数只能指示事件循环完成,将它们想象成设置标志,并不是真正自行退出任何事情 - 因此它们与C风格的API exit()s不同。

请注意,QTimer本身就是QObject。出于性能方面的考虑,如果计时器经常发生火灾,使用QBasicTimer这个简单的包装函数可以更方便地使用QObject::startTimer()返回的定时器ID。然后,您将重新执行QObject::timerEvent()。这样避免了信号插槽调用的开销。根据经验,直接(非排队)信号插槽调用的花费大约是将两个1000个字符的QStrings连接在一起。见my benchmark

备注:如果您发布简短的示例,可能会更容易直接发布整个代码,以便将来不会丢失 - 只要它在一个文件中。在三个文件中散布100行示例代码是适得其反的。见下文,关键是filename.cpp的末尾#include "filename.moc"。它也有助于一次性定义和声明这些方法,即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" 
相关问题