Prevent blocking main eventLoop via QueuedConnection?
-
I have a ComboBox, which should trigger longer lasting work when activated.
My goal is to update the GUI, showing some "Loading" information and close the ComboBox, the do some work, while the UI may stay unresponsive in the meantime.I can't get the ComboBox to close and update the GUI elements the way I expect it: from what I have read so far, using Signals&Slots with QueuedConnection should provide what I want.
But it seems, that I need to add many qApp->processEvent() calls while doing work to get an early update in the GUI.
Is there another way (not using Threads or Timers) to finish updating the GUI and start work afterwards? Also, I am seeking for a better understanding why QueuedConnection does not help in this case? Is there any literature which explains the connection between Renderer / QML / Main threads and their synchronization regarding event handling?
Is it correct, saying that Signals&Slots with QueuedConnection do not differ from QMetaObject::invokeMethod with QueuedConnection (as used in the commented code below) regarding the event posting and handling?Application
class Application : public QObject { Q_OBJECT QML_NAMED_ELEMENT(App) public: Application(QObject *parent = nullptr) { QObject::connect(this, &Application::startWork, this, &Application::_doWork, Qt::QueuedConnection); QObject::connect(this, &Application::doneLoading, this, &Application::finish); } ~Application() {}; Q_INVOKABLE void doWork() { } Q_INVOKABLE bool isLoading() const { return m_isLoading; } Q_PROPERTY(bool isLoading READ isLoading NOTIFY isLoadingChanged) signals: void startWork(); void doneLoading(); void isLoadingChanged(); private slots: //void _doWork() { setIsLoading(true); QMetaObject::invokeMethod(this, &Application::work, Qt::QueuedConnection); } void _doWork() { setIsLoading(true); work(); } void finish() { setIsLoading(false); } private: bool m_isLoading = false; void setIsLoading(bool value) { if (value == m_isLoading) return; m_isLoading = value; emit isLoadingChanged(); } void work() { // GUI is never updated qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers); std::this_thread::sleep_for(std::chrono::milliseconds(4000)); // GUI will update after first or second iteration //for (auto i = 0; i < 4; ++i) { // qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers); // std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // qDebug("..."); //} emit doneLoading(); } };QML part:
Window { id: root width: 400 height: 300 visible: true title: qsTr("Test") RowLayout { id: row anchors.centerIn: parent Label { id: lblState; text: "Normal" } ComboBox { id: cmb model: ["First", "Second", "Third"] onActivated: { app.startWork() console.log("Combo activated -> work requested") } } states : [ State { name: "normal"; when: !app.isLoading }, State { name: "loading"; when: app.isLoading } ] onStateChanged: { console.log("Loading: " + app.isLoading) lblState.text = app.isLoading ? "Loading" : "Normal" } } } -
I have a ComboBox, which should trigger longer lasting work when activated.
My goal is to update the GUI, showing some "Loading" information and close the ComboBox, the do some work, while the UI may stay unresponsive in the meantime.I can't get the ComboBox to close and update the GUI elements the way I expect it: from what I have read so far, using Signals&Slots with QueuedConnection should provide what I want.
But it seems, that I need to add many qApp->processEvent() calls while doing work to get an early update in the GUI.
Is there another way (not using Threads or Timers) to finish updating the GUI and start work afterwards? Also, I am seeking for a better understanding why QueuedConnection does not help in this case? Is there any literature which explains the connection between Renderer / QML / Main threads and their synchronization regarding event handling?
Is it correct, saying that Signals&Slots with QueuedConnection do not differ from QMetaObject::invokeMethod with QueuedConnection (as used in the commented code below) regarding the event posting and handling?Application
class Application : public QObject { Q_OBJECT QML_NAMED_ELEMENT(App) public: Application(QObject *parent = nullptr) { QObject::connect(this, &Application::startWork, this, &Application::_doWork, Qt::QueuedConnection); QObject::connect(this, &Application::doneLoading, this, &Application::finish); } ~Application() {}; Q_INVOKABLE void doWork() { } Q_INVOKABLE bool isLoading() const { return m_isLoading; } Q_PROPERTY(bool isLoading READ isLoading NOTIFY isLoadingChanged) signals: void startWork(); void doneLoading(); void isLoadingChanged(); private slots: //void _doWork() { setIsLoading(true); QMetaObject::invokeMethod(this, &Application::work, Qt::QueuedConnection); } void _doWork() { setIsLoading(true); work(); } void finish() { setIsLoading(false); } private: bool m_isLoading = false; void setIsLoading(bool value) { if (value == m_isLoading) return; m_isLoading = value; emit isLoadingChanged(); } void work() { // GUI is never updated qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers); std::this_thread::sleep_for(std::chrono::milliseconds(4000)); // GUI will update after first or second iteration //for (auto i = 0; i < 4; ++i) { // qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers); // std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // qDebug("..."); //} emit doneLoading(); } };QML part:
Window { id: root width: 400 height: 300 visible: true title: qsTr("Test") RowLayout { id: row anchors.centerIn: parent Label { id: lblState; text: "Normal" } ComboBox { id: cmb model: ["First", "Second", "Third"] onActivated: { app.startWork() console.log("Combo activated -> work requested") } } states : [ State { name: "normal"; when: !app.isLoading }, State { name: "loading"; when: app.isLoading } ] onStateChanged: { console.log("Loading: " + app.isLoading) lblState.text = app.isLoading ? "Loading" : "Normal" } } }@enel said in Prevent blocking main eventLoop via QueuedConnection?:
QueuedConnection does not help in this case?
Because it only helps when using signals/slots across different threads. As soon as your slot is started in GUI thread you GUI is blocked. If you don't want to use threads then the only way I see is to execute qApp->processEvents regularly while doing your heavy work.
-
Thanks for clarification!
woboq blog mentions (Qt Event Loop): "A QueuedConnection will post an event to the event loop to eventually be handled. ... If the receiver is in the same thread, the event will be processed later, as the event loop iterates." This led to my expectation, that the event is eventually handled in the next iteration, therefore closing the Combo could be done before.
-
Thanks for clarification!
woboq blog mentions (Qt Event Loop): "A QueuedConnection will post an event to the event loop to eventually be handled. ... If the receiver is in the same thread, the event will be processed later, as the event loop iterates." This led to my expectation, that the event is eventually handled in the next iteration, therefore closing the Combo could be done before.
@enel said in Prevent blocking main eventLoop via QueuedConnection?:
that the event is eventually handled in the next iteration
But if the next iteration is delayed because of blocked event loop it will happen later.
-
@enel said in Prevent blocking main eventLoop via QueuedConnection?:
that the event is eventually handled in the next iteration
But if the next iteration is delayed because of blocked event loop it will happen later.
-
E enel has marked this topic as solved on