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>