I just wrote some QThread based code that executes a big calculation. To visualize the progress I need to open a QProgressDialog. The dialog is application modal (using open()) since I do not want to allow modifications of the main window during calculation. The thread emits various signals that allow state machine based communication between GUI and thread.
Two of the signals emitted by the thread's worker object are "Progress" and "Finished". If "Progress" is emitted I am updating the QProgressDialog using setValue(). If "Finished" is emitted the dialog is destroyed.
The following happens at the end of calculation:
- "Progress" event (100%) is emitted
- "Finished" is emitted directly after
- setValue(100) is called due to "Progress" event
- Because the dialog is modal, setValue() calls processEvents()
- processEvents() delivers the "Finished" event
- The "Finished" event causes the Dialog to be destroyed in the middle of setValue() which causes a crash
QProgressDialog breaks my architecture by calling processEvents() in setValue(). Also my coding conventions forbid usage of any nested event loops (like in exec() etc.).
I have two questions:
Why does a modal dialog require a nested event loop? From my undestanding blocking the parent windows' input seem not to require this.
Is it possible to use QProgressDialog in a modal way but without a nested event loop?
you should use
deleteLater()
to destroy yourQProgressDialog
. The event that deletes yourQProgressDialog
object is handled within a function that belongs to theQProgressDialog
object itself, this boils down to the legitimacy of callingdelete this;
within a c++ member function, you can refer to this question from the isocpp C++ FAQ for more information about this. The gist of it is that you should guarantee that you no longer access any member of the object after committing suicide...since you cannot guarantee this in the Qt's
QProgressDialog::setValue()
implementation, an event thatdelete
s theQProgressBar
like that will happily invoke UB on the next access to any member of the object (when picked up within a member function).deleteLater
was specifically designed to solve this kind of issue as deferred delete events are handled in a special manner (they are not picked up byQCoreApplication::processEvents()
). This means that theQProgressDialog
object will be destroyed aftersetValue
returns control into the event loop and not in the middle of executingsetValue
...Always use
deleteLater
in situations like this. When using plaindelete
within an event, you have to make sure that this event doesn't get handled while executing a member function of this object, and that it doesn't get executed as a result of emitting a signal from this object (with a direct signal/slot connection) since, after all, a signal is just a member function whose implementation is provided by Qt's MOC)...