Reputation: 9124
... called from a static class and non-main thread. In short, I have a class "sapp", which has another static class "tobj" as a static member. To avoid static order initialization fiasco, tobj is declared inside sapp's method, which in turn, returns pointer of tobj's instance. My problem is that, tobj has a timer which should be started in the constructor, and tobj may be created by non-main thread. QTimer can't be started by a thread other than main thread (or the one which doesn't have event loop i guess). for that reason, I invoke QTimer::start via QMetaObject::invokeMethod + Qt::QueuedConnection to avoid thread problem, however it doesn't work, QTimer::start is never invoked. I investigated a bit the problem and looks like, QTimer::start is not invoked because QTimer's parent(tobj in this case) is declared as static. If I declare tobj as a non static member, everything works fine.
I don't understand quite well the internals of Qt, could this be a bug or I'm doing something wrong?
here's the code:
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;
}
Here's a link to the test project, consisting of 1 header and 1 cpp file http://dl.dropbox.com/u/3055964/untitled.zip
I'm testing on Qt 4.8.0 and MSVC 2010.
Thank you very much, your help is much appreciated.
Upvotes: 4
Views: 5091
Reputation: 98445
I think you're overdoing it. Qt's beauty lies in the fact that what you try to do is very easy.
You seem to think that a QTimer
will somehow magically interrupt your running thread to execute the callback. This is never the case in Qt. In Qt, all events and signals are delivered to the event loop running in the thread, and that event loop dispatches them to QObjects. Thus within a thread, there are no concurrency hazards due to Qt's event and signal/slot framework. Also, as long as you depend on Qt's signal-slot and event mechanisms to communicate between threads, you don't need to use any other access control primitives as within each thread things are serialized.
So, your problem is that you never run an event loop in your thread, so the timeout event will never be picked up and dispatched to the slot you've connected to the timeout()
signal.
A QTimer
can be created and started in any thread, as long as the thread where you start it is the thread that the timer's QObject
lives in. QObjects belong to a thread you create them in, unless you move them to a different thread using QObject::moveToThread(QThread*)
.
The use of invokeMethod
to start the timer is entirely unnecessary. You're starting the timer from the same thread it lives in, after all. A queued signal-slot connection will queue the signals in a queue, as the name implies. Specifically, when you signal, a QMetaCallEvent
is queued for the QObject where the receiver slot lives. An event loop must run in the slot object's thread to pick this up and execute the call. You never call anything in the thread to empty that queue, thus there's nothing to call your timeout()
slot.
As for the static member initialization fiasco, you're free to build your entire T object in main()
or within a QObject that lives in the main thread, and then move it to a new thread -- that's how one would do it when not using QtConcurrent. When using QtConcurrent, your runnable function can construct and destruct any number of QObjects.
To fix your code, the lambda should spin an event loop, thus:
[] () {
sapp s;
s.f();
QEventLoop l;
l.exec();
}
Below I attach a SSCCE example of how it'd be idiomatically done in Qt. If one doesn't want to use QtConcurrent, then there'd be two changes: you'd want to qApp->exit()
right after you exit()
the thread's event loop -- otherwise, a.exec()
will never exit (how would it know to?). You'd also want to wait()
on the thread before exiting the main()
function - it's bad style to destroy QThreads
that are still running.
The exit()
functions only signal the event loops to finish, think of them as setting a flag, not really exiting anything by themselves -- so they are different from C-style API exit()s
.
Note that a QTimer
is a QObject
in and of itself. For performance reasons, if the timer fires often, it's much cheaper to use a QBasicTimer
that is a simple wrapper around the timer id returned by QObject::startTimer()
. You'd then reimplement QObject::timerEvent()
. You avoid the overhead of a signal-slot call that way. As a rule of thumb, direct (non-queued) signal-slot calls cost about as much as concatenating two 1000 character QStrings together. See my benchmark.
Side note: if you're posting short examples, it may be easier to post the entire code direcly, so that it's not lost in the future -- as long as it's all in one file. It's counterproductive to have a 100 lines of example code strewn across three files. See below, the key is to #include "filename.moc"
at the end of filename.cpp
. It also helps to define and declare the methods all at once, Java-style. All in the name of keeping it short and easy to follow. It's an example, after all.
//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"
Upvotes: 5