Qt 5.1 Свойство QML через потоки

В целях разрешения я создал TestApp, которые повторяют ту же проблему, что и я.

Я переношу свое программное обеспечение с Qt 4.8 на Qt 5.1.

Моя первая программа была многопоточной и плавно работала с QML, при условии, что классы были потокобезопасными. Но теперь я получаю это сообщение:

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).

Это код, который воспроизводит ошибку:

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();
        }
    }
}

Моя программа довольно сложная (много свойств и классов для совместного использования с интерфейсом), и мне не хотелось бы создавать класс интерфейса только для подключения моих свойств... Есть ли у вас какие-либо предложения по этот вопрос?

Ответ 1

В Qt5 вам не нужно вставлять приложение самостоятельно, движок QML 2 уже многопоточен, поэтому просто запустите QQuickView, выложите части С++, которые вам нужны для контекста, установите в него файл QML и показать(). Это достаточно. Не пытайтесь самостоятельно модифицировать поток QML, это действительно сложнее, чем в QML1.

Ответ 2

Различные вещи здесь:

  • Перемещение QTimers между потоками проблематично, особенно если они запущены

  • Вторичный поток никогда не вызывает exec(), поэтому проживающий в нем QTimer не срабатывает (но я подозреваю, что он срабатывает из-за его создания, а затем с помощью moveToThread

  • Атрибуты вашего свойства, безусловно, будут вызваны механизмом QML в его потоке (т.е. основной поток в этом примере), поэтому они должны быть потокобезопасными, как вы заявили.

Мое предложение состояло в том, чтобы реструктурировать вещи, чтобы полностью не использовать moveToThread, а затем посмотреть, какие проблемы остаются.