PDA

View Full Version : QTimer within QThread



Eos Pengwern
22nd February 2011, 11:05
I have an application where I need to have both an event loop and an explicit forever {} loop running within a QThread. An object which is created inside the forever {} loop (but only once, of course) contains a QTimer which is supposed to call one of the object's own methods when it times out.

That's probably as clear as mud, so here is some code:


class updater : public QObject
{
Q_OBJECT
..
void startSomething()
{
...
if (!updateTimer)
{
updateTimer = new QTimer(this);
updateTimer -> setSingleShot(true);
updateTimer -> setInterval(UPDATE_INTERVAL);
connect(updateTimer, SIGNAL(timeout()),
this, SLOT(doSomething()), Qt::DirectConnection);
}
updateTimer -> start();
}

public slots:
void doSomething()
{
qDebug() << "Doing something";
...
}

private:
QTimer *updateTimer;

}

class worker : public QThread
{
Q_OBJECT

...

private slots:
void mainLoop()
{
forever
{
...
if (flagSet)
{
ud = new updater();
unsetFlag();
}
....

if (anotherFlagSet)
{
ud -> startSomething();
unsetTheOtherFlag();
}
}
}

private:
void run()
{
{
QTimer trigger;
connect(&trigger, SIGNAL(timeout()),
this, SLOT(mainServerLoop()), Qt::DirectConnection);
trigger.setSingleShot(true);
trigger.setInterval(0);
trigger.start();

exec();
}

updater *ud;
}

The reason for such an obtuse structure is that the 'worker' QThread needs to control its own queue of requests for things it should work on, as the main thread sometimes sends it requests which, if not executed within a certain time, become obsolete and need to be deleted from the queue before new requests are sent. I realise that, were it not for this, I could just send requests to it via queued signals and everything would be much simpler.

Anyway, the reason for having the exec() command in the run() routine, as well as the forever{} loop, is to provide an event loop so that, if I understand correctly, the QTimer which belongs to the 'ud' object (of class 'updater') can work properly.

My problem, however, is that the QTimer which belongs to the 'ud' object does not, in fact, work properly; at least not all the time. The very first time that 'startSomething' is called, it works just as it is supposed to, but on subsequent occasions it doesn't seem to work at all. I have tried commenting-out the "updateTimer -> setSingleShot(true);" line, and putting "updateTimer -> stop()" into 'doSomething()' instead, and that makes no difference.

Can anyone help me to see what may be going wrong?

Thank you,
Stephen.

wysota
22nd February 2011, 12:16
You don't need the forever loop. You can always substitute it with a timer with a 0 timeout. I can't help you more based on what you posted because your code doesn't make much sense and it's hard to guess what you really wanted.

Eos Pengwern
22nd February 2011, 13:04
Thank you for taking a look at this; unfortunately the actual code is intrinsically complex and I guess I haven't been successful in simplifying it for the question.

I don't quite understand how the forever{} loop can be substituted by a timer with a 0 timeout; can you point me to an example of this? I know about the trick of using a zero-second timer to manage GUI responsiveness in a single-thread application, but I expect you mean something different.

I'm using the forever{} loop because when I wrote this code I followed the Mandelbrot example (http://doc.qt.nokia.com/latest/threads-mandelbrot.html) in the Qt documentation, which is quite close to what I'm actually doing here. All I'm really trying to do is to add a single-shot QTimer within the rendering thread, so that the image updates itself after a certain interval. To be really precise, what I'm trying to do is to add a single short QTimer within the calculation thread which causes the image to update itself a certain time after the user stops interacting with the GUI; in other words, if the user does something with the GUI while the QTimer is still counting, the QTimer is meant to reset itself back to zero and start counting again.

wysota
22nd February 2011, 13:23
I don't quite understand how the forever{} loop can be substituted by a timer with a 0 timeout; can you point me to an example of this? I know about the trick of using a zero-second timer to manage GUI responsiveness in a single-thread application, but I expect you mean something different.

It's quite the same. The only difference is you want your "worker thread" to be "responsive" and not your "GUI thread".

The code you posted can be changed to:

class Loop : public QObject {
Q_OBJECT
public slots:
void loopIteration() {
...
if (flagSet)
{
ud = new updater();
unsetFlag();
}
....

if (anotherFlagSet)
{
ud -> startSomething();
unsetTheOtherFlag();
}

}
};

QThread thread;
thread.start();
QTimer timer;
Loop loop;
loop.moveToThread(&thread);
connect(&timer, SIGNAL(timeout()), &loop, SLOT(loopIteration());
timer.start(0);
timer.moveToThread(&thread); // optional

ServerLoop server;
server.moveToThread(&thread);
QTimer intervalTimer;
connect(&intervalTimer, ..., &server, ...);
intervalTimer.start(INTERVAL);
intervalTimer.mvoeToThread(&thread); // optional

But your code can be simplfied even more because your forever loop does nothing unless some flag is set. So you can implement slots for setting the flags that will do the task when they are requested.


class DoItAll : public QObject {
Q_OBJECT
private slots:
void createUpdater() {
ud = new updater;
}
void executeStartSomething() {
if(!ud) return;
ud->startSomething();
}
private:
...
};

QThread thread;
thread.start();
DoItAll inASimpleWay;
connect(..., ..., &inASimpleWay, SLOT(createUpdater()));
connect(..., ..., &inASimpleWay, SLOT(startSomething()));
inASimpleWay.moveToThread(&thread);

and that's it. Instead of the two connect() statements you can use QMetaObject::invokeMethod() to trigger each of the methods on demand. It can even be a public method of the class itself:

void DoItAll::raiseUpdaterFlag() {
QMetaObject::invokeMethod(this, "createUpdater", Qt::QueuedConnection);
}

Then you can call it from any thread and it will work as expected.

inASimpleWay.raiseUpdaterFlag();

Eos Pengwern
22nd February 2011, 13:50
I think I'm slowly beginning to catch on... As it happens, I'm about to grab a few hours' sleep and then catch an 11-hour flight, but if my battery holds out I'll have a close look at this during the flight and, hopefully, home in on the solution.

Thank you again for your help.

Eos Pengwern
23rd February 2011, 18:42
Fortunately my seat had a socket (ANA are good!), and by the time I had refactored my class without the forever{} loop, according to your second suggestion (using QMetaObject::invokeMethod as well) I'd got as far as St Petersburg (en route from Tokyo to London).

Anyway, it works now; the QTimer events within the thread are behaving just as they should.

I found two difficulties with QMetaObject::invokeMethod which added considerably to the size of the task:
- I found that the methods invoked did in fact need to be declared as actual slots: it wouldn't work if they were ordinary public methods.
- most of my methods took pointers as arguments, and QMetaObject::invokeMethod didn't seem to like that. I had to change things around to use const references instead. I also had be to be really careful to declare my custom structures with qRegisterMetaType, just like I would for a queued signal-slot connection.

Now, though, everything is working, so thank you very much.

wysota
23rd February 2011, 21:00
- I found that the methods invoked did in fact need to be declared as actual slots: it wouldn't work if they were ordinary public methods.
Yes. Or they have to be accompanied by the Q_INVOKABLE macro.


- most of my methods took pointers as arguments, and QMetaObject::invokeMethod didn't seem to like that. I had to change things around to use const references instead. I also had be to be really careful to declare my custom structures with qRegisterMetaType, just like I would for a queued signal-slot connection.
If you wanted all that to work across threads, you'd have to eventually do it anyway.