[Qt-interest] Controlling when timers run

Andre Somers andre at familiesomers.nl
Fri Jan 15 10:46:17 CET 2010


Sean Harmer wrote:
> 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!
>
>   

Possible, but I would incorporate all this in a special QTimer subclass 
instead. Instead of using the standard QTimer, use your own subclass. In 
that subclass, you can check for the existance of dialog boxes using 
QApplication::topLevelWidgets and decide not to execute your code (that 
is, not to emit the signal but defer processing to a later point in 
time) if a QDialog-derived top level widget exists. Of course, that 
would also stop processing during harmless dialog boxes. Not sure if 
that is a problem in your case.

Does that help?

André





More information about the Qt-interest-old mailing list