Passing custom type pointers between QML and threads via signals and slots cause app to crash
-
Hi, as topic partially described, I send pointers to my custom type object, which has 3 members (int, int and QVariant) via sig/slot between the main thread and the worker thread. I use the
Controller - Worker
approach from qt docs.Everything was fine when I have two
int
members in class, and it started crashing (SIGSEGV
) since I have addedQVariant
member to exchange data between QML and C++.I'm guessing that crash is caused by accessing the shared resource (my custom class instance). I tried to add some serialization using QMutex, with and without QMutexLocker, QReadWriteLocker but the result was the same - crash. I also tried to protect the shared resource by applying
Monitor
construct wiki protecting the data with mutexes, but it also didn't work. At the moment I'm out of ideas and look for any help. Thanks in advance.Here is my code (plain text and link to git clone), program will probably crash after clicking the button or few spontaneous clicks.
ContractorTask
is my custom which I want to send using pointers.source:
// ContractorTask.h #ifndef TASK_H #define TASK_H #include <QObject> #include <QVariant> #include <QThread> #include <QDebug> class ContractorTask : public QObject { Q_OBJECT public: explicit ContractorTask(QObject *parent = nullptr) : QObject(parent), taskID(-1), contractorID(-1), data("") { // ... } Q_INVOKABLE int getTaskID() { qDebug() << "getTaskID()" << QThread::currentThreadId(); return taskID; } Q_INVOKABLE void setTaskID(int _ID) { qDebug() << "setTaskID()" << QThread::currentThreadId(); taskID = _ID; } Q_INVOKABLE int getConctractorID() { qDebug() << "getConctractorID()" << QThread::currentThreadId(); return contractorID; } Q_INVOKABLE void setContractorID(int _ID) { qDebug() << "setContractorID()" << QThread::currentThreadId(); contractorID = _ID; } Q_INVOKABLE QVariant getData() { qDebug() << "getData()" << QThread::currentThreadId(); return data; } Q_INVOKABLE void setData(QVariant _data) { qDebug() << "setData()" << QThread::currentThreadId(); data = _data; } private: int taskID; int contractorID; QVariant data; }; #endif // TASK_H // Contractor.h #ifndef CONTRACTOR_H #define CONTRACTOR_H #include <QObject> #include <QQmlEngine> #include <QThread> #include <QMutex> #include <ContractorTask.h> //#define CONTRACTOR_DEBUG_LOCK class Contractor : public QObject { Q_OBJECT public: Contractor(int _ID, QObject* parent = nullptr); ~Contractor(); enum class Tasks : int { NOT_DEFINED = -1, Q_LIST_VARIANTS_TO_QML, GET_QVARIANT_FROM_QML }; Q_ENUMS(Tasks) inline static void registerTasks() { qmlRegisterUncreatableType<Contractor>("Tasks", 1, 0, "ContractorTasks", "ContractorTask is uncreatable in QML"); } int getID() { return ID; } public slots: void executeTask(ContractorTask* _task); signals: void finished(); void taskStarted(ContractorTask* _task); void taskFinished(ContractorTask* _task); private: int ID; #ifndef CONTRACTOR_DEBUG_LOCK Qt::HANDLE mainThreadID; #endif }; #endif // CONTRACTOR_H //Contractor.cpp #include "Contractor.h" #include <QDebug> #include <QThread> Contractor::Contractor(int _ID, QObject *parent) : QObject(parent), ID(_ID) { #ifndef CONTRACTOR_DEBUG_LOCK mainThreadID = QThread::currentThreadId(); #endif } Contractor::~Contractor() { // ... qDebug() << "Cleaning SerialPortContractor..."; } void Contractor::executeTask(ContractorTask* _task) { emit(taskStarted(_task)); qDebug() << "\tCONTRACTOR STARTED"; int localContractorID = _task->getConctractorID(); if(localContractorID != getID()) { qDebug() << "Not mine ID: " << localContractorID << "discarding"; return; } qDebug() << "Emiting signal from Contractor"; emit(taskStarted(_task)); int localTaskID = _task->getTaskID(); QVariant localData = _task->getData(); QList<QVariant> _params; QList<float> _floats; _params = localData.value<QList<QVariant>>(); for(auto item : _params) { _floats << item.toFloat(); } #ifndef CONTRACTOR_DEBUG_LOCK qDebug() << "Main ThreadID =" << mainThreadID << "my ThreadID =" << QThread::currentThreadId(); #endif switch(static_cast<Tasks>(localTaskID)) { case Tasks::Q_LIST_VARIANTS_TO_QML : { QList<QVariant> _params; _params.append(12.5F); _params.append(14.36F); QVariant _data = _params; _task->setData(_data); } break; case Tasks::GET_QVARIANT_FROM_QML: { int i = 0; for(auto item : _floats) { qDebug() << "C++ item" << i++ << "value" << item; } } break; default: { #ifndef CONTRACTOR_DEBUG_LOCK qDebug() << "Oh... I don't have these one :("; #endif } } emit(taskFinished(_task)); qDebug() << "\tCONTRACTOR ENDED"; } // Controller.h #ifndef CONTROLLERv2_H #define CONTROLLERv2_H #include <QObject> #include <QThread> #include <QDebug> #include <Contractor.h> #include <ContractorTask.h> class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Contractor *worker = new Contractor(0); worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::startTask, worker, &Contractor::executeTask); connect(worker, &Contractor::taskFinished, this, &Controller::handleResults); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } signals: void startTask(ContractorTask*); public slots: void handleResults(ContractorTask* _task) { qDebug() << _task->getTaskID() << _task->getConctractorID() << _task->getData(); } }; #endif // CONTROLLERv2_H // main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include <Controller.h> #include <QQmlContext> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); Contractor::registerTasks(); Controller TaskController; ContractorTask Task; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("TaskController", &TaskController); engine.rootContext()->setContextProperty("Task", &Task); const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); } // main.qml import QtQuick 2.12 import QtQuick.Controls 2.5 import Tasks 1.0 ApplicationWindow { id: applicationWindow visible: true width: 640 height: 480 title: qsTr("Test") Button { id: button text: qsTr("Button") anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter onClicked: { var items = [1.25, 2.12345, 2.2, 1.25, 2.12345, 2.2, 1.25, 2.12345, 2.2, 1.25, 2.12345, 2.2] Task.setData(items) runTaskFromQML(ContractorTasks.GET_QVARIANT_FROM_QML, 0) runTaskFromQML(ContractorTasks.GET_QVARIANT_FROM_QML, 0) runTaskFromQML(ContractorTasks.GET_QVARIANT_FROM_QML, 0) runTaskFromQML(ContractorTasks.GET_QVARIANT_FROM_QML, 0) } } function runTaskFromQML(_taskId, _contractorID) { console.log("Running worker task from QML, ID = ", _taskId, " Contractor ID = ", _contractorID) Task.setTaskID(_taskId) Task.setContractorID(_contractorID) TaskController.startTask(Task) } } // .pro QT += quick CONFIG += c++11 DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ Contractor.cpp \ main.cpp RESOURCES += qml.qrc # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target HEADERS += \ Contractor.h \ ContractorTask.h \ Controller.h
-
OK as @J.Hilk said:
@J.Hilk said in Passing custom type pointers between threads via signals and slots cause app to crash:
are you by any chance exposing those thread-shared custom types directly to qml?
And:
@J.Hilk said in Passing custom type pointers between QML and threads via signals and slots cause app to crash:
I can only tell you that I always ran into trouble when I tried to access/manipulate c++ threaded stuff (directly)via QML.
Indeed that was causing the crashes. The solution is to create a copy of the resource that is sent from QML and then send a copy of that resource to thread.
As @J.Hilk suggested Manager object should do the job which is:
@Matthew11 said in [Passing custom type pointers between QML and threads via signals and slots cause app to crash
- Manager <-> Threads :
- communicate via signal/slots with QueuedConnection
- synchronization/locking on the shared resource
- Manager <-> QML
- signals and slots
- or directly from the manager's memory
Below you can find my working example. This is very similar to the code which I provided in the first post. You send a dispatch from QML to specific Contractor, Contractor then is doing his job and return the result back to QML (sends task with input data scenario). Or you send a dispatch to Contractor to retrieve some data (send task with no input data scenario). ContractorTask is no longer exposed to QML. But pointers are no longer send however it is possible in the C++ domain (across main (Manager) and workers threads with proper locking/synchronization).
If you want to feel how it is when app is crashing uncomment the line _taskCopy.setData(_data); from pushTaskToContractor() in Controller.h which disabling the step of making the copy of the resource.
Thank you all for your help in solving the problem!
Code
://.pro QT += quick CONFIG += c++11 SOURCES += \ Contractor.cpp \ main.cpp RESOURCES += qml.qrc HEADERS += \ Contractor.h \ ContractorTask.h \ Controller.h // Contractor.h #ifndef CONTRACTOR_H #define CONTRACTOR_H #include <QObject> #include <ContractorTask.h> class Contractor : public QObject { Q_OBJECT public: Contractor(int _ID, QObject* parent = nullptr); int getID() { return ID; } public slots: void executeTask(ContractorTask _task); signals: void finished(); void taskStarted(ContractorTask _task); void taskFinished(ContractorTask _task); private: int ID; }; #endif // CONTRACTOR_H // Contractor.cpp #include "Contractor.h" #include <QDebug> #include <QThread> Contractor::Contractor(int _ID, QObject *parent) : QObject(parent), ID(_ID) {} void Contractor::executeTask(ContractorTask _task) { emit(taskStarted(_task)); if(getID() != _task.getConctractorID()) { qDebug() << "Not mine ID, discarding"; return; } QVariant localData = _task.getData(); switch(_task.getTaskID()) { case 0: // PASS TASK TO C++ TO RETRIEVE DATA { QList<QVariant> _params; _params.append(12.5F); _params.append(14.36F); QVariant _data = _params; _task.setData(_data); qDebug() << "PASS TASK TO C++ TO RETRIEVE DATA"; } break; case 1: // PASS TASK WITH DATA TO C++ AND GET THE SAME DATA BACK IN QML { QList<QVariant> _params; _params = localData.value<QList<QVariant>>(); QList<float> _floats; int counter = 0; for(auto item : _params) { _floats << item.toFloat(); qDebug() << "Getting data in C++ from QML (QList<float>): item =" << counter++ << "value =" << item; } qDebug() << "PASS TASK WITH DATA TO C++ AND GET THE SAME DATA BACK IN QML"; } break; default: { qDebug() << "Oh... I don't have these one :("; } } emit(taskFinished(_task)); } // ContractorTask.h #ifndef CONTRACTORTASK_H #define CONTRACTORTASK_H #include <QVariant> class ContractorTask { public: ContractorTask() : taskID(-1), contractorID(-1), data("") {} int getTaskID() { return taskID; } void setTaskID(int _ID) {taskID = _ID; } int getConctractorID() { return contractorID; } void setContractorID(int _ID) { contractorID = _ID; } QVariant getData() { return data; } void setData(QVariant _data) { data = _data; } private: int taskID; int contractorID; QVariant data; }; Q_DECLARE_METATYPE(ContractorTask) #endif // CONTRACTORTASK_H // Controller.h #ifndef CONTROLLER #define CONTROLLER #include <QObject> #include <QThread> #include <QDebug> #include <Contractor.h> #include <ContractorTask.h> class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Contractor *worker = new Contractor(0); worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::startTask, worker, &Contractor::executeTask); connect(worker, &Contractor::taskStarted, this, &Controller::contractorStartedTask); connect(worker, &Contractor::taskFinished, this, &Controller::contractorFinishedTask); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } signals: void startTask(ContractorTask); void taskStarted(int id, int contractor, QVariant data); void taskEnded(int id, int contractor, QVariant data); public slots: void pushTaskToContractor(int _id, int _contractor, QVariant _data) { // QVariant depends to QML, so make COPY of QVariant CONTENT, before passing it to thread: QList<QVariant> _params; _params = _data.value<QList<QVariant>>(); QVariant _dataToSend = _params; ContractorTask _taskCopy; _taskCopy.setTaskID(_id); _taskCopy.setContractorID(_contractor); _taskCopy.setData(_dataToSend); // Sending local data copy is OK // _taskCopy.setData(_data); // Sending _data (has source in QML) = PROGRAM CRASH!!! emit(startTask(_taskCopy)); } void contractorFinishedTask(ContractorTask _task) { // Passing COPY of ContractorTask to QML: emit(taskEnded(_task.getTaskID(), _task.getConctractorID(), _task.getData())); } void contractorStartedTask(ContractorTask _task) { // Passing COPY of ContractorTask to QML: emit(taskStarted(_task.getTaskID(), _task.getConctractorID(), _task.getData())); } }; #endif // CONTROLLER // main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include <Controller.h> #include <QQmlContext> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); Controller TaskController; qRegisterMetaType<ContractorTask>(); QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("TaskController", &TaskController); const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); // Get item from QML, and connect it's signal (startTaskFromQML) to Controller QObject *item = engine.rootObjects().first(); QObject::connect(item, SIGNAL(startTaskFromQML(int, int, QVariant)), &TaskController, SLOT(pushTaskToContractor(int, int, QVariant))); return app.exec(); } // main.qml import QtQuick 2.12 import QtQuick.Controls 2.5 ApplicationWindow { id: root visible: true width: 640 height: 480 title: qsTr("Test") signal startTaskFromQML(int id, int contractor, variant data) property variant _data: 0 Connections { target: TaskController onTaskEnded: console.log("Contractor with ID =", contractor, "finished task with ID = ", id, "and returned result:", data); onTaskStarted: console.log("Contractor with ID =", contractor, "started task with ID = ", id); } Column { id: column anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter Button { id: passAndGet text: qsTr("PASS TASK WITH DATA TO C++ AND GET THE SAME DATA BACK IN QML") anchors.horizontalCenter: parent.horizontalCenter onClicked: { _data= [1.2, 3.4, 5.6, 7.8] for(var i = 0; i < 50; i++) { root.startTaskFromQML(1, 0, _data) } } } Button { id: getData text: qsTr("PASS TASK TO C++ TO RETRIEVE DATA") anchors.horizontalCenter: parent.horizontalCenter onClicked: { _data = 0 root.startTaskFromQML(0, 0, _data) } } } } // qtquickcontrols2.conf [Controls] Style=Material // qml.qrc <RCC> <qresource prefix="/"> <file>main.qml</file> <file>qtquickcontrols2.conf</file> </qresource> </RCC>
-
Hi,
What is the exact goal for that QVariant ?
-
@Matthew11 said in Passing custom type pointers between threads via signals and slots cause app to crash:
I'm guessing that crash is caused by accessing the shared resource (my custom class instance). I tried to add some serialization using QMutex, with and without QMutexLocker, QReadWriteLocker but the result was the same - crash.
Crashes have stack traces. Please provide one. Also as a hint:
Q_INVOKABLE void setData(QVariant _data) { Q_ASSERT(QThread::currentThread() == thread()); qDebug() << "setData()" << QThread::currentThreadId(); data = _data; }
-
@SGaist said in Passing custom type pointers between threads via signals and slots cause app to crash:
What is the exact goal for that QVariant ?
Well, simply speaking, to pass various data between QML and C++ (floats, integers, QStrings and any that will be needed) within the ContractorTask object.
Use of case: The UI send request (with taskID, contractorID (both ints) and data (QVariant) as ContractorTask object) then the specific Contractor (another thread) run specific code and return the result (QVariant) which is also included in ContractorTask object. Then in QML, I know which task was started and ended and with what data -> the UI can be updated. As well in the opposite direction (C++ received event, then sends data to QML). I also have interfaces (not included in the sources that I provided) for Contractor and Controller which relay on ContractorTask object and simplifies things with making new Contractors passing them to Controller which takes care of connections, clean up (things that Controller does).
-
@kshegunov said in Passing custom type pointers between threads via signals and slots cause app to crash:
Crashes have stack traces. Please provide one. Also as a hint:
Q_ASSERT(QThread::currentThread() == thread());
So, If I understand it correctly, it gives me insights where and when another thread is working on resources from another thread? So it happens when
Contractor
is working on_task
inContractor::executeTask(ContractorTask*)
which is "normal" in this case. And these places should be protected. Am I right, that's the hint?Returning to stack traces, I generated them for the same code included before. App crashes seem to be connected with QV4Object. 3'rd and 6'th seem to be more common. Is the table content enough or will it be better with screenshots?
-->1<-- 1 QV4::Object::get qv4object_p.h 308 0x7ffff714c2fc 2 QV4::Runtime::method_callProperty qv4runtime.cpp 1376 0x7ffff714c2fc 3 QV4::Moth::VME::interpret qv4vme_moth.cpp 718 0x7ffff70e3f90 4 QV4::Moth::VME::exec qv4vme_moth.cpp 441 0x7ffff70e7cc4 5 QV4::ArrowFunction::virtualCall qv4functionobject.cpp 513 0x7ffff7080720 6 QV4::FunctionObject::call qv4functionobject_p.h 202 0x7ffff71474a3 7 QV4::Runtime::method_callName qv4runtime.cpp 1346 0x7ffff71474a3 8 QV4::Moth::VME::interpret qv4vme_moth.cpp 745 0x7ffff70e3e37 9 QV4::Moth::VME::exec qv4vme_moth.cpp 441 0x7ffff70e7cc4 10 QV4::Function::call qv4function.cpp 68 0x7ffff707fade 11 QQmlJavaScriptExpression::evaluate qqmljavascriptexpression.cpp 216 0x7ffff71e7eb8 12 QQmlBoundSignalExpression::evaluate qqmlboundsignal.cpp 225 0x7ffff718aa52 13 QQmlBoundSignal_callback qqmlboundsignal.cpp 358 0x7ffff718bf53 14 QQmlNotifier::emitNotify qqmlnotifier.cpp 106 0x7ffff71c9cf4 15 QQmlData::signalEmitted qqmlengine.cpp 880 0x7ffff716e274 16 QMetaObject::activate qobject.cpp 3648 0x7ffff6a20c92 17 QQuickAbstractButtonPrivate::handleRelease qquickabstractbutton.cpp 179 0x7fffd0d71ca8 18 QQuickControl::mouseReleaseEvent qquickcontrol.cpp 2099 0x7fffd0d8c9f5 19 QQuickItem::event qquickitem.cpp 8096 0x7fffd22c98de 20 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1061 0x7ffff69f4c18 --> 2 <-- 1 QV4::IdentifierHash::lookup qv4identifier.cpp 173 0x7ffff7058c28 2 QV4::IdentifierHash::value qv4identifier_p.h 165 0x7ffff70a6bc5 3 QV4::QQmlContextWrapper::virtualGet qv4qmlcontext.cpp 196 0x7ffff70a6bc5 4 QV4::Object::get qv4object_p.h 314 0x7ffff7055b65 5 QV4::ExecutionContext::getProperty qv4context.cpp 360 0x7ffff7055b65 6 QV4::Runtime::method_loadName qv4runtime.cpp 985 0x7ffff7143df0 7 QV4::Moth::VME::interpret qv4vme_moth.cpp 548 0x7ffff70e413e 8 QV4::Moth::VME::exec qv4vme_moth.cpp 441 0x7ffff70e7cc4 9 QV4::ArrowFunction::virtualCall qv4functionobject.cpp 513 0x7ffff7080720 10 QV4::FunctionObject::call qv4functionobject_p.h 202 0x7ffff71474a3 11 QV4::Runtime::method_callName qv4runtime.cpp 1346 0x7ffff71474a3 12 QV4::Moth::VME::interpret qv4vme_moth.cpp 745 0x7ffff70e3e37 13 QV4::Moth::VME::exec qv4vme_moth.cpp 441 0x7ffff70e7cc4 14 QV4::Function::call qv4function.cpp 68 0x7ffff707fade 15 QQmlJavaScriptExpression::evaluate qqmljavascriptexpression.cpp 216 0x7ffff71e7eb8 16 QQmlBoundSignalExpression::evaluate qqmlboundsignal.cpp 225 0x7ffff718aa52 17 QQmlBoundSignal_callback qqmlboundsignal.cpp 358 0x7ffff718bf53 18 QQmlNotifier::emitNotify qqmlnotifier.cpp 106 0x7ffff71c9cf4 19 QQmlData::signalEmitted qqmlengine.cpp 880 0x7ffff716e274 20 QMetaObject::activate qobject.cpp 3648 0x7ffff6a20c92 --> 3 <-- 1 QV4::Heap::String::append qv4string.cpp 235 0x7ffff714dcdc 2 QV4::Heap::String::simplifyString qv4string.cpp 184 0x7ffff714de22 3 QV4::String::createPropertyKeyImpl qv4string.cpp 172 0x7ffff714dfcb 4 QV4::StringOrSymbol::createPropertyKey qv4string_p.h 311 0x7ffff714c324 5 QV4::StringOrSymbol::toPropertyKey qv4string_p.h 316 0x7ffff714c324 6 QV4::Object::get qv4object_p.h 308 0x7ffff714c324 7 QV4::Runtime::method_callProperty qv4runtime.cpp 1376 0x7ffff714c324 8 QV4::Moth::VME::interpret qv4vme_moth.cpp 718 0x7ffff70e3f90 9 QV4::Moth::VME::exec qv4vme_moth.cpp 441 0x7ffff70e7cc4 10 QV4::ArrowFunction::virtualCall qv4functionobject.cpp 513 0x7ffff7080720 11 QV4::FunctionObject::call qv4functionobject_p.h 202 0x7ffff71474a3 12 QV4::Runtime::method_callName qv4runtime.cpp 1346 0x7ffff71474a3 13 QV4::Moth::VME::interpret qv4vme_moth.cpp 745 0x7ffff70e3e37 14 QV4::Moth::VME::exec qv4vme_moth.cpp 441 0x7ffff70e7cc4 15 QV4::Function::call qv4function.cpp 68 0x7ffff707fade 16 QQmlJavaScriptExpression::evaluate qqmljavascriptexpression.cpp 216 0x7ffff71e7eb8 17 QQmlBoundSignalExpression::evaluate qqmlboundsignal.cpp 225 0x7ffff718aa52 18 QQmlBoundSignal_callback qqmlboundsignal.cpp 358 0x7ffff718bf53 19 QQmlNotifier::emitNotify qqmlnotifier.cpp 106 0x7ffff71c9cf4 20 QQmlData::signalEmitted qqmlengine.cpp 880 0x7ffff716e274 21 QMetaObject::activate qobject.cpp 3648 0x7ffff6a20c92 22 QQuickAbstractButtonPrivate::handleRelease qquickabstractbutton.cpp 179 0x7fffd0d71ca8 23 QQuickControl::mouseReleaseEvent qquickcontrol.cpp 2099 0x7fffd0d8c9f5 24 QQuickItem::event qquickitem.cpp 8096 0x7fffd22c98de 25 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1061 0x7ffff69f4c18 26 QCoreApplication::sendEvent qcoreapplication.cpp 1451 0x7ffff69f4dce 27 QQuickWindowPrivate::deliverMouseEvent qquickwindow.cpp 1784 0x7fffd22e23bf 28 QQuickWindowPrivate::deliverPointerEvent qquickwindow.cpp 2346 0x7fffd22e36fb 29 QQuickWindowPrivate::handleMouseEvent qquickwindow.cpp 2210 0x7fffd22e4535 30 QWindow::event qwindow.cpp 2336 0x7ffff77044eb 31 QQuickWindow::event qquickwindow.cpp 1673 0x7fffd22e54e5 32 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1061 0x7ffff69f4c18 33 QCoreApplication::sendSpontaneousEvent qcoreapplication.cpp 1463 0x7ffff69f4dde 34 QGuiApplicationPrivate::processMouseEvent qguiapplication.cpp 2102 0x7ffff76f91e7 35 QGuiApplicationPrivate::processWindowSystemEvent qguiapplication.cpp 1837 0x7ffff76fa795 36 QWindowSystemInterface::sendWindowSystemEvents qwindowsysteminterface.cpp 1068 0x7ffff76d644b 37 xcbSourceDispatch qxcbeventdispatcher.cpp 105 0x7ffff014767a 38 g_main_context_dispatch 0x7ffff24dc417 39 ?? 0x7ffff24dc650 40 g_main_context_iteration 0x7ffff24dc6dc 41 QEventDispatcherGlib::processEvents qeventdispatcher_glib.cpp 422 0x7ffff6a4bdcf 42 QEventLoop::exec qeventloop.cpp 225 0x7ffff69f357a 43 QCoreApplication::exec qcoreapplication.cpp 1364 0x7ffff69fbf80 44 main main.cpp 28 0x55555555be66 --> 4 <-- 1 QV4::Object::get qv4object_p.h 310 0x7ffff713cee4 2 objectToVariant qv4engine.cpp 1389 0x7ffff713cee4 3 toVariant qv4engine.cpp 1359 0x7ffff713d949 4 QV4::ExecutionEngine::toVariant qv4engine.cpp 1252 0x7ffff713dc10 5 QJSValue::toVariant qjsvalue.cpp 714 0x7ffff70521e4 6 convertJSValueToVariantType<QList<QVariant>> qv8engine.cpp 100 0x7ffff721b4c1 7 QtPrivate::ConverterFunctor<QJSValue, QList<QVariant>, QList<QVariant> ( *)(QJSValue const&)>::convert qmetatype.h 398 0x7ffff721b162 8 QMetaType::convert qmetatype.cpp 739 0x7ffff6a0b36d 9 (anonymous namespace)::convert qvariant.cpp 393 0x7ffff6a3ada1 10 QtPrivate::QVariantValueHelper<QList<QVariant>>::metaType qvariant.h 725 0x55555555a42e 11 QtPrivate::MetaTypeInvoker<QtPrivate::QVariantValueHelper<QList<QVariant>>, QVariant const&, QList<QVariant>>::invoke qvariant.h 115 0x5555555594c7 12 QtPrivate::QVariantValueHelperInterface<QList<QVariant>>::invoke qvariant.h 793 0x5555555589f0 13 qvariant_cast<QList<QVariant>> qvariant.h 860 0x55555555a536 14 QVariant::value<QList<QVariant>> qvariant.h 362 0x555555559576 15 Contractor::executeTask Contractor.cpp 44 0x555555557a9a 16 QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<ContractorTask *>, void, void (Contractor:: *)(ContractorTask *)>::call qobjectdefs_impl.h 152 0x55555555e49b 17 QtPrivate::FunctionPointer<void (Contractor:: *)(ContractorTask *)>::call<QtPrivate::List<ContractorTask *>, void> qobjectdefs_impl.h 185 0x55555555e179 18 QtPrivate::QSlotObject<void (Contractor:: *)(ContractorTask *), QtPrivate::List<ContractorTask *>, void>::impl qobjectdefs_impl.h 414 0x55555555de01 19 QObject::event qobject.cpp 1249 0x7ffff6a21581 20 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1061 0x7ffff69f4c18 21 QCoreApplication::sendEvent qcoreapplication.cpp 1451 0x7ffff69f4dce 22 QCoreApplicationPrivate::sendPostedEvents qcoreapplication.cpp 1800 0x7ffff69f7647 23 QCoreApplication::sendPostedEvents qcoreapplication.cpp 1654 0x7ffff69f7b28 24 postEventSourceDispatch qeventdispatcher_glib.cpp 276 0x7ffff6a4c793 25 g_main_context_dispatch 0x7ffff24dc417 26 ?? 0x7ffff24dc650 27 g_main_context_iteration 0x7ffff24dc6dc 28 QEventDispatcherGlib::processEvents qeventdispatcher_glib.cpp 422 0x7ffff6a4bdcf 29 QEventLoop::exec qeventloop.cpp 225 0x7ffff69f357a 30 QThread::exec qthread.cpp 531 0x7ffff682b7dc 31 QThreadPrivate::start qthread_unix.cpp 361 0x7ffff682cd13 32 start_thread pthread_create.c 463 0x7ffff5bdd6db 33 clone clone.S 95 0x7ffff5f1688f --> 5 <-- 1 QV4::Object::ownPropertyKeys qv4object_p.h 367 0x7ffff713cbdd 2 QV4::ObjectIterator::ObjectIterator qv4objectiterator_p.h 80 0x7ffff713cbdd 3 objectToVariant qv4engine.cpp 1397 0x7ffff713cbdd 4 toVariant qv4engine.cpp 1359 0x7ffff713d949 5 QV4::ExecutionEngine::toVariant qv4engine.cpp 1252 0x7ffff713dc10 6 QJSValue::toVariant qjsvalue.cpp 714 0x7ffff70521e4 7 convertJSValueToVariantType<QList<QVariant>> qv8engine.cpp 100 0x7ffff721b4c1 8 QtPrivate::ConverterFunctor<QJSValue, QList<QVariant>, QList<QVariant> ( *)(QJSValue const&)>::convert qmetatype.h 398 0x7ffff721b162 9 QMetaType::convert qmetatype.cpp 739 0x7ffff6a0b36d 10 (anonymous namespace)::convert qvariant.cpp 393 0x7ffff6a3ada1 11 QtPrivate::QVariantValueHelper<QList<QVariant>>::metaType qvariant.h 725 0x55555555a42e 12 QtPrivate::MetaTypeInvoker<QtPrivate::QVariantValueHelper<QList<QVariant>>, QVariant const&, QList<QVariant>>::invoke qvariant.h 115 0x5555555594c7 13 QtPrivate::QVariantValueHelperInterface<QList<QVariant>>::invoke qvariant.h 793 0x5555555589f0 14 qvariant_cast<QList<QVariant>> qvariant.h 860 0x55555555a536 15 QVariant::value<QList<QVariant>> qvariant.h 362 0x555555559576 16 Contractor::executeTask Contractor.cpp 44 0x555555557a9a 17 QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<ContractorTask *>, void, void (Contractor:: *)(ContractorTask *)>::call qobjectdefs_impl.h 152 0x55555555e49b 18 QtPrivate::FunctionPointer<void (Contractor:: *)(ContractorTask *)>::call<QtPrivate::List<ContractorTask *>, void> qobjectdefs_impl.h 185 0x55555555e179 19 QtPrivate::QSlotObject<void (Contractor:: *)(ContractorTask *), QtPrivate::List<ContractorTask *>, void>::impl qobjectdefs_impl.h 414 0x55555555de01 20 QObject::event qobject.cpp 1249 0x7ffff6a21581 ... <More> --> 6 <-- 1 QV4::Heap::String::append qv4string.cpp 235 0x7ffff714dcdc 2 QV4::Heap::String::simplifyString qv4string.cpp 184 0x7ffff714de22 3 QV4::Heap::StringOrSymbol::createHashValue qv4string.cpp 245 0x7ffff714e185 4 QV4::Heap::StringOrSymbol::hashValue qv4string_p.h 97 0x7ffff70ceb35 5 QV4::Heap::String::isEqualTo qv4string_p.h 128 0x7ffff70ceb35 6 QV4::String::equals qv4string_p.h 203 0x7ffff70cb3db 7 QV4::QObjectWrapper::getQmlProperty qv4qobjectwrapper.cpp 283 0x7ffff70cb3db 8 QV4::QObjectWrapper::getQmlProperty qv4qobjectwrapper.cpp 414 0x7ffff70cbc4e 9 QV4::QQmlContextWrapper::virtualGet qv4qmlcontext.cpp 246 0x7ffff70a6c66 10 QV4::Object::get qv4object_p.h 314 0x7ffff7055b65 11 QV4::ExecutionContext::getProperty qv4context.cpp 360 0x7ffff7055b65 12 QV4::Runtime::method_loadName qv4runtime.cpp 985 0x7ffff7143df0 13 QV4::Moth::VME::interpret qv4vme_moth.cpp 548 0x7ffff70e413e 14 QV4::Moth::VME::exec qv4vme_moth.cpp 441 0x7ffff70e7cc4 15 QV4::ArrowFunction::virtualCall qv4functionobject.cpp 513 0x7ffff7080720 16 QV4::FunctionObject::call qv4functionobject_p.h 202 0x7ffff71474a3 17 QV4::Runtime::method_callName qv4runtime.cpp 1346 0x7ffff71474a3 18 QV4::Moth::VME::interpret qv4vme_moth.cpp 745 0x7ffff70e3e37 19 QV4::Moth::VME::exec qv4vme_moth.cpp 441 0x7ffff70e7cc4 20 QV4::Function::call qv4function.cpp 68 0x7ffff707fade ... <More>
-
@Matthew11 said in Passing custom type pointers between threads via signals and slots cause app to crash:
So, If I understand it correctly, it gives me insights where and when another thread is working on resources from another thread?
Indeed.
So it happens when
Contractor
is working on_task
inContractor::executeTask(ContractorTask*)
which is "normal" in this case. And these places should be protected. Am I right, that's the hint?Yes, although I'm not quite convinced you're going to manage that really gracefully. Why isn't the task something which belongs to a specific thread (i.e. moved to and operated on) by a single thread?
Returning to stack traces, I generated them for the same code included before. App crashes seem to be connected with QV4Object. 3'rd and 6'th seem to be more common.
Look down. These come from QML, there're calls into your objects that originate from different threads. I imagine that's because the
Task
has no notion of ownership. I'm not much into QML but it seems you get your code called from different threads, which is a bad idea.Is the table content enough or will it be better with screenshots?
It's fine.
-
@kshegunov said in Passing custom type pointers between threads via signals and slots cause app to crash:
Yes, although I'm not quite convinced you're going to manage that really gracefully.
This is why when I adopted mutexes in
Contractor::executeTask(ContractorTask*)
or insideContractorTask
it's still crashing? Or maybe it wasn't done well by me?@kshegunov said in Passing custom type pointers between threads via signals and slots cause app to crash:
Why isn't the task something which belongs to a specific thread (i.e. moved to and operated on) by a single thread?
So, when
ContractorTask
is shared (and called from different threads), it dramatically cut down the number of connections via threads and QML but also makes it vulnerable. And by using QVariant I can put there various data types from standard types to custom types, which I need in order to present user or send user input to the worker (and then further) which also cut down the number ofconnect
's. And yeah, (correct me if I am wrong) I can do this with countable numbers of signals/slots passing different parameters withQueuedConnection
and passing the databy value
which is a (AFAIK) safe solution. But firstly I would like to give a try with using the sharaed resource and make it safe. -
@Matthew11
are you by any chance exposing those thread-shared custom types directly to qml?If yes, then that is a bad idea and my very well be the reason for your issues.
-
@J.Hilk said in Passing custom type pointers between threads via signals and slots cause app to crash:
@Matthew11 are you by any chance exposing those thread-shared custom types directly to qml?
If yes, then that is a bad idea and my very well be the reason for your issues.
Unfortunately yes, and it actually happening:
// main.cpp: engine.rootContext()->setContextProperty("Task", &Task);
And in QML I am working directly on that shared resources:
// main.qml: Task.setTaskID(_taskId) Task.setContractorID( TaskController.startTask(Task)
OK, that's the good clue. Could you tell more why is that? Is it connected with JS / qml engine stuff?
-
@Matthew11
you should probably wrap that in manager class instead.Could you tell more why is that? Is it connected with JS / qml engine stuff
most likely. I don't know enough about the inner workings to give you a definite answer. I can only tell you that I always ran into trouble when I tried to access/manipulate c++ threaded stuff (directly)via QML.
Since than I always have a manger object that forwards stuff via signals or from its own memory when exposed directly.
-
@J.Hilk said in Passing custom type pointers between threads via signals and slots cause app to crash:
you should probably wrap that in manager class instead.
Since than I always have a manger object that forwards stuff via signals or from its own memory when exposed directly.I was going to do it like this.
Let's clarify some aspects of your approach.
- Manager <-> Threads :
- communicate via signal/slots with QueuedConnection
- synchronization/locking on the shared resource
@J.Hilk said in Passing custom type pointers between threads via signals and slots cause app to crash:
manger object that forwards stuff via signals or from its own memory when exposed directly.
- Manager <-> QML
- signals and slots
- or directly from manager's memory
Have I figured it out properly?
-
@Matthew11
jep, pretty much.
That at least is how I (would)do it. -
OK as @J.Hilk said:
@J.Hilk said in Passing custom type pointers between threads via signals and slots cause app to crash:
are you by any chance exposing those thread-shared custom types directly to qml?
And:
@J.Hilk said in Passing custom type pointers between QML and threads via signals and slots cause app to crash:
I can only tell you that I always ran into trouble when I tried to access/manipulate c++ threaded stuff (directly)via QML.
Indeed that was causing the crashes. The solution is to create a copy of the resource that is sent from QML and then send a copy of that resource to thread.
As @J.Hilk suggested Manager object should do the job which is:
@Matthew11 said in [Passing custom type pointers between QML and threads via signals and slots cause app to crash
- Manager <-> Threads :
- communicate via signal/slots with QueuedConnection
- synchronization/locking on the shared resource
- Manager <-> QML
- signals and slots
- or directly from the manager's memory
Below you can find my working example. This is very similar to the code which I provided in the first post. You send a dispatch from QML to specific Contractor, Contractor then is doing his job and return the result back to QML (sends task with input data scenario). Or you send a dispatch to Contractor to retrieve some data (send task with no input data scenario). ContractorTask is no longer exposed to QML. But pointers are no longer send however it is possible in the C++ domain (across main (Manager) and workers threads with proper locking/synchronization).
If you want to feel how it is when app is crashing uncomment the line _taskCopy.setData(_data); from pushTaskToContractor() in Controller.h which disabling the step of making the copy of the resource.
Thank you all for your help in solving the problem!
Code
://.pro QT += quick CONFIG += c++11 SOURCES += \ Contractor.cpp \ main.cpp RESOURCES += qml.qrc HEADERS += \ Contractor.h \ ContractorTask.h \ Controller.h // Contractor.h #ifndef CONTRACTOR_H #define CONTRACTOR_H #include <QObject> #include <ContractorTask.h> class Contractor : public QObject { Q_OBJECT public: Contractor(int _ID, QObject* parent = nullptr); int getID() { return ID; } public slots: void executeTask(ContractorTask _task); signals: void finished(); void taskStarted(ContractorTask _task); void taskFinished(ContractorTask _task); private: int ID; }; #endif // CONTRACTOR_H // Contractor.cpp #include "Contractor.h" #include <QDebug> #include <QThread> Contractor::Contractor(int _ID, QObject *parent) : QObject(parent), ID(_ID) {} void Contractor::executeTask(ContractorTask _task) { emit(taskStarted(_task)); if(getID() != _task.getConctractorID()) { qDebug() << "Not mine ID, discarding"; return; } QVariant localData = _task.getData(); switch(_task.getTaskID()) { case 0: // PASS TASK TO C++ TO RETRIEVE DATA { QList<QVariant> _params; _params.append(12.5F); _params.append(14.36F); QVariant _data = _params; _task.setData(_data); qDebug() << "PASS TASK TO C++ TO RETRIEVE DATA"; } break; case 1: // PASS TASK WITH DATA TO C++ AND GET THE SAME DATA BACK IN QML { QList<QVariant> _params; _params = localData.value<QList<QVariant>>(); QList<float> _floats; int counter = 0; for(auto item : _params) { _floats << item.toFloat(); qDebug() << "Getting data in C++ from QML (QList<float>): item =" << counter++ << "value =" << item; } qDebug() << "PASS TASK WITH DATA TO C++ AND GET THE SAME DATA BACK IN QML"; } break; default: { qDebug() << "Oh... I don't have these one :("; } } emit(taskFinished(_task)); } // ContractorTask.h #ifndef CONTRACTORTASK_H #define CONTRACTORTASK_H #include <QVariant> class ContractorTask { public: ContractorTask() : taskID(-1), contractorID(-1), data("") {} int getTaskID() { return taskID; } void setTaskID(int _ID) {taskID = _ID; } int getConctractorID() { return contractorID; } void setContractorID(int _ID) { contractorID = _ID; } QVariant getData() { return data; } void setData(QVariant _data) { data = _data; } private: int taskID; int contractorID; QVariant data; }; Q_DECLARE_METATYPE(ContractorTask) #endif // CONTRACTORTASK_H // Controller.h #ifndef CONTROLLER #define CONTROLLER #include <QObject> #include <QThread> #include <QDebug> #include <Contractor.h> #include <ContractorTask.h> class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Contractor *worker = new Contractor(0); worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::startTask, worker, &Contractor::executeTask); connect(worker, &Contractor::taskStarted, this, &Controller::contractorStartedTask); connect(worker, &Contractor::taskFinished, this, &Controller::contractorFinishedTask); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } signals: void startTask(ContractorTask); void taskStarted(int id, int contractor, QVariant data); void taskEnded(int id, int contractor, QVariant data); public slots: void pushTaskToContractor(int _id, int _contractor, QVariant _data) { // QVariant depends to QML, so make COPY of QVariant CONTENT, before passing it to thread: QList<QVariant> _params; _params = _data.value<QList<QVariant>>(); QVariant _dataToSend = _params; ContractorTask _taskCopy; _taskCopy.setTaskID(_id); _taskCopy.setContractorID(_contractor); _taskCopy.setData(_dataToSend); // Sending local data copy is OK // _taskCopy.setData(_data); // Sending _data (has source in QML) = PROGRAM CRASH!!! emit(startTask(_taskCopy)); } void contractorFinishedTask(ContractorTask _task) { // Passing COPY of ContractorTask to QML: emit(taskEnded(_task.getTaskID(), _task.getConctractorID(), _task.getData())); } void contractorStartedTask(ContractorTask _task) { // Passing COPY of ContractorTask to QML: emit(taskStarted(_task.getTaskID(), _task.getConctractorID(), _task.getData())); } }; #endif // CONTROLLER // main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include <Controller.h> #include <QQmlContext> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); Controller TaskController; qRegisterMetaType<ContractorTask>(); QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("TaskController", &TaskController); const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); // Get item from QML, and connect it's signal (startTaskFromQML) to Controller QObject *item = engine.rootObjects().first(); QObject::connect(item, SIGNAL(startTaskFromQML(int, int, QVariant)), &TaskController, SLOT(pushTaskToContractor(int, int, QVariant))); return app.exec(); } // main.qml import QtQuick 2.12 import QtQuick.Controls 2.5 ApplicationWindow { id: root visible: true width: 640 height: 480 title: qsTr("Test") signal startTaskFromQML(int id, int contractor, variant data) property variant _data: 0 Connections { target: TaskController onTaskEnded: console.log("Contractor with ID =", contractor, "finished task with ID = ", id, "and returned result:", data); onTaskStarted: console.log("Contractor with ID =", contractor, "started task with ID = ", id); } Column { id: column anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter Button { id: passAndGet text: qsTr("PASS TASK WITH DATA TO C++ AND GET THE SAME DATA BACK IN QML") anchors.horizontalCenter: parent.horizontalCenter onClicked: { _data= [1.2, 3.4, 5.6, 7.8] for(var i = 0; i < 50; i++) { root.startTaskFromQML(1, 0, _data) } } } Button { id: getData text: qsTr("PASS TASK TO C++ TO RETRIEVE DATA") anchors.horizontalCenter: parent.horizontalCenter onClicked: { _data = 0 root.startTaskFromQML(0, 0, _data) } } } } // qtquickcontrols2.conf [Controls] Style=Material // qml.qrc <RCC> <qresource prefix="/"> <file>main.qml</file> <file>qtquickcontrols2.conf</file> </qresource> </RCC>