Beware of nested event loops
-
I already heard before to not use nested event loops if it can be avoided. Something strange might happen. After decades of Qt development I have now finally run into a problem with nested event loops.
How do we get nested event loops? Let's start with a main window and let it create a dialog. Something like this:
void MainWindow::some_menu_entry_triggered() { QDialog dlg(this); ... // set up dialog dlg.exec(); // that's how I usually start a dialog (maybe check for returned value) }Now, I put in that first dialog a button, that when clicked opens a second dialog. And to keep everything clean, I hide the first dialog for as long as the second is open. So, within
some_menu_entry_triggered()I might have something like this:connect(dlg_button, &QPushButton::clicked, this, [&]() { QDialog dlg2(this); dlg.hide(); dlg2.exec(); dlg.show(); // nested event loop fail! });The first dialog will never again show up. However, it is still modal and will block all interaction with the main window! And the problem is with nested event loops.
This problem arises with the following programming pattern (which I followed because it is used extensively in our software (maybe also because it was ported from wxWidgets at some point)):
if(dlg.exec() == QDialog::Accepted) { // do stuff }For other reasons I already had to handle the click on the OK button differently by connecting to the
QDialog::acceptedsignal. That is whyif(dlg.exec() == QDialog::Accepted)already degenerated to justdlg.exec(). If you start fresh, you should always connect toQDialog::accepted.dlg.exec()can be replaced bydlg.show()(anddlg.setModel(true)if that is what you really want). But, this introduces a separate problem: The lifetime of thedlgvariable is too short. In general, I prefer to have my lifetimes automatically managed by putting variable on the stack. In this case we have to do this differently:QDialog *dlg = new QDialog(this); connect(dlg, &QDialog::finished, dlg, &QDialog::deleteLater); // further dlg setup dlg->show();This is at least how I have solved the nested event loop problem (using
show()instead ofexec()for both dialogs).So, here you have a real world example when nested event loops fail.
-
Here's another way to cause a fail: https://qt-project.atlassian.net/browse/QTBUG-116984
-
I already heard before to not use nested event loops if it can be avoided. Something strange might happen. After decades of Qt development I have now finally run into a problem with nested event loops.
How do we get nested event loops? Let's start with a main window and let it create a dialog. Something like this:
void MainWindow::some_menu_entry_triggered() { QDialog dlg(this); ... // set up dialog dlg.exec(); // that's how I usually start a dialog (maybe check for returned value) }Now, I put in that first dialog a button, that when clicked opens a second dialog. And to keep everything clean, I hide the first dialog for as long as the second is open. So, within
some_menu_entry_triggered()I might have something like this:connect(dlg_button, &QPushButton::clicked, this, [&]() { QDialog dlg2(this); dlg.hide(); dlg2.exec(); dlg.show(); // nested event loop fail! });The first dialog will never again show up. However, it is still modal and will block all interaction with the main window! And the problem is with nested event loops.
This problem arises with the following programming pattern (which I followed because it is used extensively in our software (maybe also because it was ported from wxWidgets at some point)):
if(dlg.exec() == QDialog::Accepted) { // do stuff }For other reasons I already had to handle the click on the OK button differently by connecting to the
QDialog::acceptedsignal. That is whyif(dlg.exec() == QDialog::Accepted)already degenerated to justdlg.exec(). If you start fresh, you should always connect toQDialog::accepted.dlg.exec()can be replaced bydlg.show()(anddlg.setModel(true)if that is what you really want). But, this introduces a separate problem: The lifetime of thedlgvariable is too short. In general, I prefer to have my lifetimes automatically managed by putting variable on the stack. In this case we have to do this differently:QDialog *dlg = new QDialog(this); connect(dlg, &QDialog::finished, dlg, &QDialog::deleteLater); // further dlg setup dlg->show();This is at least how I have solved the nested event loop problem (using
show()instead ofexec()for both dialogs).So, here you have a real world example when nested event loops fail.
@SimonSchroeder
The following code:#include <QApplication> #include <QBoxLayout> #include <QDialog> #include <QPushButton> int main(int argc, char *argv[]) { QApplication a(argc, argv); QDialog dlg1; dlg1.setWindowTitle("dlg1"); dlg1.setLayout(new QVBoxLayout()); QPushButton *button = new QPushButton("Click"); dlg1.layout()->addWidget(button); QObject::connect(button, &QPushButton::clicked, &dlg1, [&]() { QDialog dlg2(&dlg1); dlg1.setWindowTitle("dlg2"); dlg1.hide(); dlg2.exec(); dlg1.show(); // nested event loop fail! }); dlg1.exec(); return a.exec(); }works fine for me. Ubuntu 24.04, Qt 6.4.2. I press the button, see the second dialog, close it, and the first dialog reappears. I can remove
&dlg1from being the argument todlg2's constructor and it still works. In what significant way does your situation differ from this? -
@SimonSchroeder
The following code:#include <QApplication> #include <QBoxLayout> #include <QDialog> #include <QPushButton> int main(int argc, char *argv[]) { QApplication a(argc, argv); QDialog dlg1; dlg1.setWindowTitle("dlg1"); dlg1.setLayout(new QVBoxLayout()); QPushButton *button = new QPushButton("Click"); dlg1.layout()->addWidget(button); QObject::connect(button, &QPushButton::clicked, &dlg1, [&]() { QDialog dlg2(&dlg1); dlg1.setWindowTitle("dlg2"); dlg1.hide(); dlg2.exec(); dlg1.show(); // nested event loop fail! }); dlg1.exec(); return a.exec(); }works fine for me. Ubuntu 24.04, Qt 6.4.2. I press the button, see the second dialog, close it, and the first dialog reappears. I can remove
&dlg1from being the argument todlg2's constructor and it still works. In what significant way does your situation differ from this?@JonB Just one wild guess what's different: In my case the event loop is also already running. You are doing
a.exec()only afterdlg1.exec(). So, this is one less nesting level.But, what I didn't mention is that I'm on Windows 11 and still using Qt 5.13.2. (We don't often change Qt versions because we compile Qt statically ourselves (and also Qwt). It takes up a lot of time and software development is not our primary business.)
@JKSH Kind of looks like essentially the same problem: The final show event seems to get lost when the inner most event loop is destroyed.
-
@JonB Just one wild guess what's different: In my case the event loop is also already running. You are doing
a.exec()only afterdlg1.exec(). So, this is one less nesting level.But, what I didn't mention is that I'm on Windows 11 and still using Qt 5.13.2. (We don't often change Qt versions because we compile Qt statically ourselves (and also Qwt). It takes up a lot of time and software development is not our primary business.)
@JKSH Kind of looks like essentially the same problem: The final show event seems to get lost when the inner most event loop is destroyed.
@SimonSchroeder said in Beware of nested event loops:
@JonB Just one wild guess what's different: In my case the event loop is also already running. You are doing a.exec() only after dlg1.exec(). So, this is one less nesting level.
FWIW, and for completeness, I changed above code from running
dlg1.exec();immediately beforea.exec()toQTimer::singleShot(3000, [&dlg1]() { dlg1.exec(); } );Behaviour is same: dialogs hide and show as expected, no missing "re-show".
It is true that I am Linux and Qt6 while you are Windows and Qt5. I don't know if one of those is the difference.