...从静态类和非主线程调用。简而言之,我有一个“ sapp”类,它还有一个静态类“ tobj”作为静态成员。为避免静态订单初始化失败,在sapp的方法中声明了tobj,该方法又返回了tobj实例的指针。我的问题是,tobj有一个应该在构造函数中启动的计时器,并且tobj可能是由非主线程创建的。QTimer不能由主线程以外的线程(或者我猜没有事件循环的线程)启动。因此,我通过QMetaObject :: invokeMethod + Qt :: QueuedConnection调用QTimer :: start来避免线程问题,但是它不起作用,从不调用QTimer :: start。我调查了一下问题,看起来像QTimer :: start没有被调用,因为QTimer' 的parent(在这种情况下为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;
}
Run Code Online (Sandbox Code Playgroud)
这是测试项目的链接,包含1个标头和1个cpp文件http://dl.dropbox.com/u/3055964/untitled.zip
我正在Qt 4.8.0和MSVC 2010上进行测试。
非常感谢您的帮助。
我认为您做得太过分了。Qt的优点在于您尝试做的事情很容易。
您似乎认为a QTimerwill会以某种方式神奇地中断正在运行的线程以执行回调。在Qt中绝不是这种情况。在Qt中,所有事件和信号都传递到线程中运行的事件循环中,并且该事件循环将它们分派给QObjects。因此,在线程内,不会由于Qt的事件和信号/插槽框架而导致并发危害。另外,只要您依靠Qt的信号插槽和事件机制在线程之间进行通信,就不需要在序列化每个线程内使用任何其他访问控制原语。
因此,您的问题是,您永远不会在线程中运行事件循环,因此永远不会拾取超时事件并将其分配到您已连接到timeout()信号的插槽。
QTimer可以在任何线程中创建和启动A ,只要您在其中启动的线程是计时器所在的线程即可QObject。QObjects属于您在其中创建它们的线程,除非您使用将该对象移至另一个线程QObject::moveToThread(QThread*)。
invokeMethod完全不需要使用启动计时器。毕竟,您是从计时器所在的同一线程启动计时器的。顾名思义,排队的信号插槽连接将使信号在队列中排队。具体来说,当您发出信号时,a QMetaCallEvent将排队到接收器插槽所在的QObject。必须在插槽对象的线程中运行一个事件循环,以进行接管并执行调用。您永远不会在线程中调用任何东西来清空该队列,因此没有什么可调用您的timeout()插槽的。
至于静态成员初始化失败,您可以自由地main()在驻留在主线程中的QObject内或内部构建整个T对象 ,然后将其移至新线程-这就是不使用QtConcurrent时的方式。使用QtConcurrent时,您的runnable函数可以构造和破坏任意数量的QObject。
要修复您的代码,lambda应该旋转事件循环,因此:
[] () {
sapp s;
s.f();
QEventLoop l;
l.exec();
}
Run Code Online (Sandbox Code Playgroud)
下面我附上一个SSCCE示例,说明如何在Qt中惯用地完成此操作。如果不希望使用QtConcurrent,那么会是两个变化:你想qApp->exit()右后你exit()的线程的事件循环-否则,a.exec()绝不会退出(如何将它知道?)。您还希望wait()在退出main()函数之前进入线程-销毁QThreads仍在运行的样式是不好的。
这些exit()功能仅表示事件循环已完成,将其视为设置标志,而不是自己真正退出任何东西-因此它们不同于C样式API exit()s。
请注意,a 本身QTimer就是a QObject。出于性能方面的考虑,如果计时器经常触发,则使用QBasicTimer,将其简单地包装为所返回的计时器ID 会便宜得多QObject::startTimer()。然后,您将重新实现QObject::timerEvent()。这样可以避免信号时隙调用的开销。根据经验,直接(非排队)信号时隙调用的成本大约是将两个1000个字符的QString连接在一起的成本。见我的基准。
旁注:如果您要发布简短的示例,则直接发布整个代码可能会更容易,这样将来就不会丢失-只要它们全部放在一个文件中即可。在三个文件中散布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"
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3770 次 |
| 最近记录: |