Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Beware of nested event loops
Qt 6.11 is out! See what's new in the release blog

Beware of nested event loops

Scheduled Pinned Locked Moved Unsolved General and Desktop
5 Posts 3 Posters 133 Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • S Offline
    S Offline
    SimonSchroeder
    wrote last edited by
    #1

    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::accepted signal. That is why if(dlg.exec() == QDialog::Accepted) already degenerated to just dlg.exec(). If you start fresh, you should always connect to QDialog::accepted.

    dlg.exec() can be replaced by dlg.show() (and dlg.setModel(true) if that is what you really want). But, this introduces a separate problem: The lifetime of the dlg variable 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 of exec() for both dialogs).

    So, here you have a real world example when nested event loops fail.

    JonBJ 1 Reply Last reply
    3
    • JKSHJ Offline
      JKSHJ Offline
      JKSH
      Moderators
      wrote last edited by
      #2

      Here's another way to cause a fail: https://qt-project.atlassian.net/browse/QTBUG-116984

      Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

      1 Reply Last reply
      1
      • S SimonSchroeder

        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::accepted signal. That is why if(dlg.exec() == QDialog::Accepted) already degenerated to just dlg.exec(). If you start fresh, you should always connect to QDialog::accepted.

        dlg.exec() can be replaced by dlg.show() (and dlg.setModel(true) if that is what you really want). But, this introduces a separate problem: The lifetime of the dlg variable 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 of exec() for both dialogs).

        So, here you have a real world example when nested event loops fail.

        JonBJ Offline
        JonBJ Offline
        JonB
        wrote last edited by JonB
        #3

        @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 &dlg1 from being the argument to dlg2's constructor and it still works. In what significant way does your situation differ from this?

        S 1 Reply Last reply
        0
        • JonBJ JonB

          @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 &dlg1 from being the argument to dlg2's constructor and it still works. In what significant way does your situation differ from this?

          S Offline
          S Offline
          SimonSchroeder
          wrote last edited by
          #4

          @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.

          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.

          JonBJ 1 Reply Last reply
          0
          • S SimonSchroeder

            @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.

            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.

            JonBJ Offline
            JonBJ Offline
            JonB
            wrote last edited by
            #5

            @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 before a.exec() to

            QTimer::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.

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved