Commit 4a867300 authored by Michal Policht's avatar Michal Policht

Run QML engine in main thread.

Many weird things are going on under the hood, when QQmlApplicationEngine
is moved to separate thread. Initially this was meant to make it easier
to implement Windows service, but it does not seem to be the most stable
solution.
parent 65afa592
......@@ -31,8 +31,6 @@ Project {
"src/cutehmi/daemon/CoreData.hpp",
"src/cutehmi/daemon/Daemon.cpp",
"src/cutehmi/daemon/Daemon.hpp",
"src/cutehmi/daemon/EngineThread.cpp",
"src/cutehmi/daemon/EngineThread.hpp",
"src/cutehmi/daemon/Exception.cpp",
"src/cutehmi/daemon/Exception.hpp",
"src/cutehmi/daemon/logging.cpp",
......
Problem:
Class QQmlApplicationEngine connects Qt.quit() signal to
QCoreApplication::quit() and QQmlApplicationEngine::exit() signal to
QCoreApplication::exit(), but it does so with AutoConnection. This causes in
some circumstances problems, which are described in Qt documentation.
"It's good practice to always connect signals to this slot using a
QueuedConnection. If a signal connected (non-queued) to this slot is emitted
before control enters the main event loop (such as before "int main" calls
exec()), the slot has no effect and the application never exits. Using a queued
connection ensures that the slot will not be invoked until after control enters
the main event loop." -- Qt documentation on QCoreApplication::exit().
Investigation:
File qtdeclarative/src/qml/qml/qqmlapplicationengine.cpp contains method
QQmlApplicationEnginePrivate::init(), which contains following lines.
```
q->connect(q, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
q->connect(q, &QQmlApplicationEngine::exit, QCoreApplication::instance(), &QCoreApplication::exit);
```
Workaround:
Disconnect signals and connect them again with QueuedConnection.
#include "EngineThread.hpp"
namespace cutehmi {
namespace daemon {
EngineThread::EngineThread(QObject * parent):
QThread(parent)
{
}
}
}
//(c)MP: Copyright © 2019, Michal Policht. All rights reserved.
//(c)MP: This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef H_TOOLS_CUTEHMI_u_DAEMON_SRC_CUTEHMI_DAEMON_ENGINETHREAD_HPP
#define H_TOOLS_CUTEHMI_u_DAEMON_SRC_CUTEHMI_DAEMON_ENGINETHREAD_HPP
#include <QThread>
namespace cutehmi {
namespace daemon {
class EngineThread:
public QThread
{
Q_OBJECT
public:
EngineThread(QObject * parent = nullptr);
signals:
void loadRequested(const QString & project);
};
}
}
#endif
//(c)MP: Copyright © 2019, Michal Policht. All rights reserved.
//(c)MP: This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
......@@ -2,7 +2,6 @@
#include "../cutehmi.dirs.hpp"
#include "cutehmi/daemon/logging.hpp"
#include "cutehmi/daemon/Daemon.hpp"
#include "cutehmi/daemon/EngineThread.hpp"
#include "cutehmi/daemon/CoreData.hpp"
#include "cutehmi/daemon/Exception.hpp"
......@@ -107,10 +106,19 @@ int main(int argc, char * argv[])
CUTEHMI_DEBUG("Library paths: " << QCoreApplication::libraryPaths());
EngineThread engineThread;
std::unique_ptr<QQmlApplicationEngine> engine(new QQmlApplicationEngine);
engine->addImportPath(baseDirPath + CUTEHMI_DIRS_EXTENSION_INSTALL_DIRNAME);
CUTEHMI_DEBUG("QML import paths: " << engine->importPathList());
QQmlApplicationEngine engine;
engine.addImportPath(baseDirPath + CUTEHMI_DIRS_EXTENSION_INSTALL_DIRNAME);
CUTEHMI_DEBUG("QML import paths: " << engine.importPathList());
//<cutehmi_daemon-1.workaround target="Qt" cause="QTBUG-73649">
// Class QQmlApplicationEngine connects Qt.quit() signal to QCoreApplication::quit() and QQmlApplicationEngine::exit()
// signal to QCoreApplication::exit(), but it does so with AutoConnection. This causes in some circumstances problems,
// which are described in Qt documentation (see QCoreApplication::exit()). Workaround is to disconnect signals and
// connect them again with QueuedConnection.
engine.disconnect(& engine, nullptr, data.app, nullptr);
engine.connect(& engine, SIGNAL(quit()), data.app, SLOT(quit()), Qt::QueuedConnection);
engine.connect(& engine, & QQmlApplicationEngine::exit, data.app, & QCoreApplication::exit, Qt::QueuedConnection);
//</cutehmi_daemon-1.workaround>
if (!data.cmd->value(data.opt->project).isNull()) {
CUTEHMI_DEBUG("Project: " << data.cmd->value(data.opt->project));
......@@ -123,22 +131,13 @@ int main(int argc, char * argv[])
// If source URL is relative (does not contain scheme), then make absolute URL: file:///baseDirPath/sourceUrl.
if (projectUrl.isRelative())
projectUrl = QUrl::fromLocalFile(baseDirPath).resolved(projectUrl);
// Check if file exists and eventually set context property.
// Check if file exists.
if (projectUrl.isLocalFile() && !QFile::exists(projectUrl.toLocalFile()))
throw Exception(QObject::tr("Project file '%1' does not exist.").arg(projectUrl.url()));
else {
engine->moveToThread(& engineThread);
QObject::connect(& engineThread, & EngineThread::loadRequested, engine.get(), QOverload<const QString &>::of(& QQmlApplicationEngine::load));
QObject::connect(& engineThread, SIGNAL(loadRequested(const QString &)), & engineThread, SLOT(start()));
QObject::connect(data.app, & QCoreApplication::aboutToQuit, & engineThread, & QThread::quit);
// Delegate management of engine to EngineThread, so that it gets deleted after thread finishes execution.
QObject::connect(& engineThread, & QThread::finished, engine.release(), & QObject::deleteLater);
emit engineThread.loadRequested(projectUrl.url());
engine.load(projectUrl.url());
int result = data.app->exec();
engineThread.wait();
engine.collectGarbage();
return result;
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment