是否可以在单独的线程中将 QObject 设置为 QML 上下文属性?

Posted

技术标签:

【中文标题】是否可以在单独的线程中将 QObject 设置为 QML 上下文属性?【英文标题】:Is it possible to have a QObject set as a QML context property in a separate thread? 【发布时间】:2018-08-28 12:04:28 【问题描述】:

我构建了一个小型测试示例,以更好地理解 Qt 5 提供的 QML/C++ 绑定。

QML 端基本上是一个StackLayout 和几个Page,它们在控制台或Label 中显示信息。用户可以使用Button 浏览这些页面。

只要一切都在一个线程中运行,就可以了。 通过QObject 的信号和槽的QML/C++ 绑定按预期工作。

但是当我尝试在另一个线程中移动暴露给 QML 的 QObject 时,我收到此消息并且应用程序被终止:

QQmlEngine: Illegal attempt to connect to BackendWorker(0xbe8a7c28) that is in a different thread than the QML engine QQmlApplicationEngine(0xbe8a7c44.

这是应用程序的主要内容:

int main(int argc, char *argv[])

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    BackendWorker backendWorker; // expose a signal and a slot only

    QQmlApplicationEngine engine;

    QQmlContext *ctx = engine.rootContext();

    ctx->setContextProperty("backendWorker", &backendWorker);

    QThread t1;
    backendWorker.moveToThread(&t1); // here is the offending part
    t1.start();

    engine.load(QUrl(QStringLiteral("qrc:/UI/main.qml")));

    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();

是否所有暴露于 QML 的QObject(带有属性、信号或槽或Q_INVOKABLE)都必须与QGuiApplication 存在于同一线程中?

编辑:

这是一个较小的示例,它显示 QStringListModel 与 QML ListView 位于一个单独的线程中并且它可以工作,那么这怎么可能?

// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QStringListModel>
#include <QQmlContext>
#include <QTimer>
#include <QThread>

class Functor

public:
    Functor(QStringListModel *model) : m_model(model)  
    void operator()() 
        QStringList list;
        list << "item 5" << "item 6" << "item 7" ;
        m_model->setStringList(list);
    
private:
    QStringListModel *m_model;
;

int main(int argc, char *argv[])

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QStringListModel listModel;

    Functor functor(&listModel);

    QQmlApplicationEngine engine;

    QQmlContext *ctx = engine.rootContext();

    ctx->setContextProperty("listModel", &listModel);

    QThread t1;
    QStringList list;
    list << "item 1" << "item 2" << "item 3" << "item 4" ;
    listModel.setStringList(list);
    listModel.moveToThread(&t1);
    t1.start();

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    QTimer::singleShot(5000, functor);

    return app.exec();

QML 方面:

// main.qml
import QtQuick 2.9
import QtQuick.Window 2.2

Window 
    visible: true

    ListView 
        width: 200
        height: 500
        anchors.centerIn: parent
        model: listModel
        delegate: Rectangle 
            height: 50
            width: 200
            Text 
                text : display
            
        
    

谢谢。

【问题讨论】:

您现在有什么问题?你用过@talamaki 解决方案吗? 请告诉我我的问题中您不清楚的地方,以便我在需要时进行澄清 在您的更新中,您指出模型没有出现问题,但我没有看到任何有趣的东西,它不会产生错误,因为您没有在新线程中做任何事情。然后你指出 BackendWorker 是错误的,但我没有看到任何进展,我也没有看到任何关于你提出的解决方案的 cmets。 他们指出的答案是 QML 不允许与另一个线程中的对象进行交互,如果要进行交互,则必须创建一个代理对象,该对象通过信号连接到对象在另一个线程中并将该数据重新发送到 QML。 我没有在我的编辑中提出任何解决方案。但是现在我很困惑,因为当另一个线程中的 listModel 发生变化时,listView 会更新。 【参考方案1】:

Qt 在 C++ 级别支持跨线程边界的连接,但在 QML 级别不支持。 QML 引擎很可能在不久的将来(如果有的话)不会支持它们。请参阅错误报告It's not possible to connect two QML objects living in different threads,该报告因超出范围而关闭。

下面,你可以看到一个简化的例子来实现一个 worker 并将其作为上下文属性注册到 QML 引擎:

class BackendWorker : public QObject 
    Q_OBJECT
  public slots:
    void operate() 
      int i = 0;
      while (i < 1000) 
        report(i++);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
      
    
  signals:
    void report(int x);
;

class BackendController : public QObject 
    Q_OBJECT
  public:
    BackendController() 
      w.moveToThread(&t);
      connect(this, &BackendController::start, &w, &BackendWorker::operate);
      connect(&w, &BackendWorker::report, this, &BackendController::report);
      t.start();
    
  signals:
    void start();
    void report(int x);
  private:
    BackendWorker w;
    QThread t;
;

int main(int argc, char *argv[])

    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    BackendController c;
    engine.rootContext()->setContextProperty("BackendController", &c);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();

【讨论】:

以上是关于是否可以在单独的线程中将 QObject 设置为 QML 上下文属性?的主要内容,如果未能解决你的问题,请参考以下文章

QObject 重入和线程安全

如何修复运行时崩溃 - QObject::setParent: 无法设置父级,新父级在不同的线程中?

是否自动在后台线程中将数据发布到 api

是否可以为 QObject 的父级使用共享指针?

QThread 与 QObject的关系(QObject可以用于多线程,可以发送信号调用存在于其他线程的slot函数,但GUI类不可重入)

从另一个线程发出信号是否安全?