Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt 5.1 QML property through Threads

For the purpose of the resolution, I've created a TestApp that repeat the same problem that I have.

I'm porting my software from Qt 4.8 to Qt 5.1.

My first program was multithreaded, and was working smoothly with QML, provided that the classes were thread safe. But now I get this message :

QObject::connect: No such slot TestApp::run() in ..\ThreadingTest\main.cpp:21
QQmlEngine: Illegal attempt to connect to TestApp(0x29cfb8) that is in a different thread than the QML engine QQmlEngine(0x2f3e0f8).

This is the code that reproduce the error :

main.cpp :

#include <QtGui/QGuiApplication>
#include <QQmlContext>
#include <QThread>
#include "qtquick2applicationviewer.h"
#include "testapp.h"

int main(int argc, char *argv[])
{
    int out;

    QGuiApplication app(argc, argv);

    QtQuick2ApplicationViewer viewer;

    TestApp * testapp = new TestApp();

    QThread * testappThread;

    testappThread = new QThread();

    QObject::connect(testappThread, SIGNAL(started()), testapp, SLOT(run()));

    testapp->moveToThread(testappThread);

    testappThread->start();

    viewer.rootContext()->setContextProperty("TestApp", testapp);

    viewer.setMainQmlFile(QStringLiteral("qml/ThreadingTest/main.qml"));
    viewer.showExpanded();

    out = app.exec();

    testappThread->quit();
    testappThread->wait();

    delete testapp;
    delete testappThread;

    return out;
}

testapp.h :

#ifndef TESTAPP_H
#define TESTAPP_H

#include <QObject>
#include <QString>
#include <QTimer>
#include <QReadWriteLock>

#define HELLOWORLD "Hello World !"

extern QReadWriteLock HelloWorldLock;

class TestApp : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString HelloWorld READ getHelloWorld WRITE setHelloWorld NOTIFY HelloWorldChanged)
public:
    explicit TestApp(QObject *parent = 0);

    virtual ~TestApp();

    QString getHelloWorld();

    void setHelloWorld(QString);

public slots:

    void run();

    void toggleHelloWorld();

signals:

    void HelloWorldChanged();

private:

    QString m_HelloWorld;
    QTimer * m_Timer;

};

#endif // TESTAPP_H

testapp.cpp :

#include "testapp.h"

QReadWriteLock HelloWorldLock(QReadWriteLock::Recursive);

TestApp::TestApp(QObject *parent) :
    QObject(parent)
{
    HelloWorldLock.lockForWrite();
    m_HelloWorld = HELLOWORLD;
    HelloWorldLock.unlock();

    m_Timer = new QTimer(this);

    connect(m_Timer, SIGNAL(timeout()), this, SLOT(toggleHelloWorld()));
}

TestApp::~TestApp() {
    m_Timer->stop();

    delete m_Timer;
}

QString TestApp::getHelloWorld() {
    HelloWorldLock.lockForRead();
    QString out = m_HelloWorld;
    HelloWorldLock.unlock();

    return out;
}

void TestApp::setHelloWorld(QString text) {
    HelloWorldLock.lockForWrite();
    m_HelloWorld = text;
    HelloWorldLock.unlock();

    emit HelloWorldChanged();
}

void TestApp::run() {
    m_Timer->start(1000);
}

void TestApp::toggleHelloWorld() {
    HelloWorldLock.lockForWrite();
    if(m_HelloWorld == "") {
        m_HelloWorld = HELLOWORLD;
    }
    else {
        m_HelloWorld = "";
    }
    HelloWorldLock.unlock();

    emit HelloWorldChanged();
}

main.qml :

import QtQuick 2.0

Rectangle {
    width: 360
    height: 360
    Text {
        text: TestApp.HelloWorld
        anchors.centerIn: parent
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }
}

My program is quite complex (a lot of properties and classes to share with the interface) and I wouldn't like to have to create an interface class just to connect my properties... Do you have any suggestions to cope with this issue ?

like image 585
Murazaki Avatar asked Aug 07 '13 19:08

Murazaki


2 Answers

You don't need to thread the app yourself cause in Qt5, the QML 2 engine is already massively multithreaded, so just launch the QQuickView, expose the C++ parts you need to the context, set the QML file in it, and show(). It's sufficient. Don't try to modify the QML thread by yourself, this is really more complex that it was in QML1.

like image 125
TheBootroo Avatar answered Nov 10 '22 11:11

TheBootroo


Various things here:

  • Moving QTimers between threads is problematic, especially if they are started

  • The secondary thread never calls exec(), so a QTimer living in it won't fire (but I suspect it does fire due to you creating it and then using moveToThread

  • Your property accessors will certainly be called by the QML engine on its thread (i.e the main thread in this example), so they need to be thread-safe as you stated.

My suggestion would be to restructure things to avoid using moveToThread entirely, and then see what issues remain.

like image 30
James Turner Avatar answered Nov 10 '22 11:11

James Turner