Modal QProgressDialog::setValue() causes crash by nested event loop

1.1k views Asked by At

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:

  1. Why does a modal dialog require a nested event loop? From my undestanding blocking the parent windows' input seem not to require this.

  2. Is it possible to use QProgressDialog in a modal way but without a nested event loop?

1

There are 1 answers

5
Mike On BEST ANSWER

you should use deleteLater() to destroy your QProgressDialog. The event that deletes your QProgressDialog object is handled within a function that belongs to the QProgressDialog object itself, this boils down to the legitimacy of calling delete 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 that deletes the QProgressBar 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 by QCoreApplication::processEvents()). This means that the QProgressDialog object will be destroyed after setValue returns control into the event loop and not in the middle of executing setValue...

Always use deleteLater in situations like this. When using plain delete 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)...