[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