[Qt-interest] Controlling when timers run

Sean Harmer sean.harmer at maps-technology.com
Fri Jan 15 00:08:09 CET 2010


Hi,

Glenn Hughes wrote:
>> Ah OK so the problems occur when you enter a local event loop such as
>> you get when calling QDialog::exec() and your timers continue to get
>> fired from that event loop?
> 
> Yes, or also possibly inside of other system calls. I haven't actually
> seen problems occur under Qt, but I know that we've had these problems
> under Mac OS, so I need to know exactly how things function in the Qt
> world.
> 
> We've often had bugs with stacks that look something like
> 
> Timer callback (accessing changed data structures... kaboom)
> OS call (like, drawing or making a window or something)
> Our code (handling an event, and changing data structures)
> OS stuff (event loop)
> main
> 
> This is because our app came from the OS 9 days (i.e. old Mac OS)
> where we had the ability to do this:
> 
> MainEventLoop()
> {
>        WaitNextEvent(ev)
>        HandleEvent(ev)
>        DoStuffInTheBackground()
> }
> 
> As you can see, "DoStuffInTheBackground" will only ever be called when
> the stack as fully unwound to the main event loop. It will never be
> called inside of "HandleEvent"
> 
> Under OS X we now have two types of timers, ones that can run any
> time, and ones that can only run when the stack is fully unwound.
> Because we have access to all our entry points, we can block the "only
> unwound" style timers whenever the OS calls into our code (i.e. from
> our Carbon event handlers)... Since all this stuff is wrapped inside
> of Qt (and rightly so), it would be great if I had some other
> mechanism for determining if the stack was fully unwound back to the
> main event loop, or not.

OK it does sound like nested event loops could be what are causing at 
least some of your problems.

As Andre correctly said, QTimer only fires events when Qt has unwound to 
an event loop. The problem is this is happening whilst you are showing 
your dialogs that ask user for some input because the dialogs use an 
event loop. Once you return from the QDialog::exec() call the timers 
will not fire again until your processing has finished and you return 
the main application event loop that you started in the main() function.

So, one possible solution to this would be to maintain a list of timers 
and either stop these or block their signals just before you call 
QDialog::exec() (or any other nested event loop) and then re-enable or 
unblock their signals once QDialog::exec() returns. This way you can be 
safe in the knowledge that the slots connected to the QTimer::timeout() 
signal will not be called whilst you are waiting for user input.

This should be a relatively simple fix if you wrap it up in a 
convenience singleton class that tracks your timers.

Something like this perhaps?

class TimerManager {
public:
	static TimerManager* instance()
	{
		if ( !ms_self )
			ms_self = new TimerManager;
		return ms_self;
	}

	void registerTimer( QTimer* t )
	{
		m_timers.append( t );
	}

	void stopTimers()
	{
		foreach ( QTimer* t, m_timers )
			t->stop();
	}


	void startTimers()
	{
		foreach ( QTimer* t, m_timers )
			t->start();
	}

protected:
	TimerManager()
	 : m_timers()
	{}
	
	static TimerManager* ms_self;
	QList<QTimer*> m_timers;
};

TimerManager* TimerManager::ms_self = 0;


Then when you create a timer, simply register it with the above class 
and surround your calls to QDialog::exec() appropriately. e.g.:

void MyClass::myFunc()
{
	// Stop the timers since we will use a nested event loop
	TimerManager::instance()->stopTimers();

	// Get some user input
	StandardButton result = QMessageBox::question( this,
		"What should I do?", "Shall I go on?",
		QMessageBox::Yes | QMessageBox::No );

	// Restart the timers now that we no longer have
	// a nested event loop
	TimerManager::instance()->startTimers();

	// Handle the request
	...
}

Hope this helps and good luck!

Sean



More information about the Qt-interest-old mailing list