QMetaObject::invokeMethod doesn't work when...
-
... 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_OBJECTQTimer timer;
private slots:
void timeout();public:
tobj();
};class sapp : public QObject
{
Q_OBJECTpublic:
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.
-
The timer is created in the QtConcurrent::run thread, so it will only timeout if that thread has an event loop running, and it isn't the case, using
Qt::QueuedConnection
doesn't change that.If you want to create the unique tobj object as soon as a sapp object is created, you can simply use an helper class:
@class tobjHelper {
static tobj* instance;
public:
tobjHelper() {
// This is not thread safe at all
if (!instance) {
// Because the QTimer needs an event loop
Q_ASSERT_X(QCoreApplication::instance(), Q_FUNC_INFO, "a QCoreApplication or QApplication object has to be instantiated");
instance = new tobj();
}
}// not static to prevent returning NULL tobj *f() const { return instance; }
};
// In the cpp:
// tobj* tobjHelper::instance = 0;class sapp : public QObject, tobjHelper
{
Q_OBJECT
};@
Because sapp inherits from tobjHelper, its default constructor will be called, and that constructor will create a tobj if one doesn't already exist. And sapp::f() will returns that tobj instance. -
@alexisdm, thank you very much for you assistance. I think it's more clear now. So to be clear, timeout never triggered because QTimer wasn't created in the thread which had an event loop right?
and the purpose of f() method is to make sure that instance member is always initialized?And lastly, in your case, isn't it possible that sapp's constructor to be called from non-main thread? in this case what happens?
Thank you very much again :)
-
[quote author="Davita" date="1335597866"]So to be clear, timeout never triggered because QTimer wasn't created in the thread which had an event loop right? [/quote]Yes.
[quote]and the purpose of f() method is to make sure that instance member is always initialized?[/quote]Yes, the purpose of f() not being static is so that a tobjHelper has already been created when we call the function, which means the instance member was also initialized.
[quote]And lastly, in your case, isn't it possible that sapp's constructor to be called from non-main thread? in this case what happens?[/quote]With my previous code, if the constructor is called from another thread, the QTimer will also belongs to that thread, and if it doesn't have an event loop, the timeout() signal won't be fired.
You could solve that with something like:
@tobj::tobj()
: timer(this) // make the timer a child object of tobj
{
// move this and its children to the main thread
moveToThread(QCoreApplication::instance()->thread());connect(&timer, SIGNAL(timeout()), this, SLOT(timeout())); timer.setInterval(500); QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection);
}@
But the instance creation is still not thread safe: if 2 sapp object are created in 2 different threads at the same time, they can both find that the instance member is NULL and each initialize it (the exact same problem(s) that you would have to initialize a singleton in a thread safe way, since it is basically a singleton pattern).
-
Thank you very much alexisdm, actually I already tried moveToThread, but it doesn't work in my case, I don't know why. I also checked that tobj didn't have parent, but still, no result. I ended up initializing static stuff outside the class itself, it's ugly but it works. I will try your suggestion soon and let you know the results :)
Thanks again