1

Under Qt5, how to wait until the main event loop has executed all slots that are connected to a specific widget?

Specifically, in the following example

class MyWidget : public QWidget {
    ~MyWidget() {
        action_A();
    }
    void myclose() {
        ...
        close();
    }
};

...
auto* w = new MyWidget;
...
w->close();
action_B();
...

I want the two functions

action_A();
action_B();

to be executed in the above order. However, without further precaution, they are executed in the reverse order.

QWidget::close() emits a signal which ultimately triggers deletion of the MyWidget instance. However, to the best of my understanding, this will only happen after control has returned to the main event loop. In any case, it will not happen before we call action_B().

Related discussions that don't help:

Joachim W
  • 7,290
  • 5
  • 31
  • 59
  • If you have single-threaded application and don't explicitly use queued connection type, signals are delivered immediately, IOW they are like method calls to all connected slots. – hyde Mar 08 '19 at 18:20
  • Possible duplicate of [Does QThread::quit() discard all events in EventQueue?](https://stackoverflow.com/questions/13684700/does-qthreadquit-discard-all-events-in-eventqueue) – Marek R Mar 08 '19 at 18:22
  • If you want something to run when a widget is closed, you should catch relevant events on that widget. Once you have instantiated `MyWidget` and set its properties, you should not do anything else: you must return control to the main event loop. Then if something else should be triggered by the closing: do trigger it by the closing. – Kuba hasn't forgotten Monica Mar 08 '19 at 18:47
  • @hyde Even in multithreaded applications, signals delivered to the same thread are invoked immediately unless the default is overridden. – jonspaceharper Mar 08 '19 at 23:29
  • So you're wondering how to determine when all signals are delivered and connections destroyed? – jonspaceharper Mar 08 '19 at 23:31
  • @hyde, @Jon Harper: As mentioned by @Marek R, `QWidget::close()` may involve `deleteLater()`, which delegates execution to the main event loop. – Joachim W Mar 08 '19 at 23:31

2 Answers2

2

Simply connect MyWidget's destroyed() signal to action_B (or something that calls it). Since action_A is called from MyWidget's destructor, it will be called before the destroyed() signal is emitted in ~QObject(), and thus, action_B called.

Note that you cannot control the timing of the call of action_A as long as it from the destructor of MyWidget, so deferring the call to action_B is your only choice here, i.e. you cannot make w->close(); action_B(); work.

jonspaceharper
  • 4,207
  • 2
  • 22
  • 42
  • This fully answers my question as it stands. However, I do not want to delay just `action_B` but `action_B` and everything that follows. Hence simply connecting `action_B` to `MyWidget::destroyed` is not enough. Rather I'll need to *wait* for `MyWidget::destroyed` before proceeding with `action_B` and what follows. – Joachim W Mar 09 '19 at 07:33
  • @JoachimW Yes, that is the point of connecting it to a slot and delaying invocation of `action_B`. It will not be called until MyWidget'` destructor is finished (along with others in the inheritance hierarchy) and `QObject`'s destructor has begun, emitting the signal. Wrap your `action_B` and what follows in a slot connected to the aforementioned signal, and you are done needing to wait for it. – jonspaceharper Mar 09 '19 at 07:51
0

Why do you think action_A() and action_B() should be called in this order?

Once the destructor is called, QObject will no longer receive any new signals (except those emitted during d-tor execution), since it will not exist and all signals and slots will be disconnected.

Since action_A() is a called from the destructor and action_B() not, action_A() is executed as last.

Note that close may (doesn't have to) call deleteLater to destroy the window and this thing schedules destruction of object in event loop.

Marek R
  • 32,568
  • 6
  • 55
  • 140