QML垃圾收集删除仍在使用的对象

QML garbage collection deletes objects still in use

本文关键字:对象 删除 QML      更新时间:2023-09-26

我曾多次遇到这个问题,无论对象是在QML还是C++中创建的,都是动态创建的。这些对象在使用中被删除,导致无明显原因的严重崩溃。这些对象仍然是其他对象的引用对象和父对象,一直到根对象,所以我觉得QML在它们的refcount仍然高于零的情况下删除这些对象很奇怪。

到目前为止,我找到的唯一解决方案是在C++中创建对象,并显式地将所有权设置为CPP,从而无法从QML中删除对象。

起初,我认为这可能是养育子女的问题,因为我使用的是QObject派生类,并且动态实例化的QML方法为父级传递Item,而QtObject甚至没有父级属性-它没有从QObject中公开。

但后来我尝试了Qobject派生,它公开并使用父子关系,最后甚至尝试使用Item,只是为了确保对象是正确的父子关系,但这种行为仍然存在。

这里有一个产生这种行为的例子,不幸的是,我无法将其扁平化为单个源,因为Component的深度嵌套破坏了它:

// ObjMain.qml
Item {
    property ListModel list : ListModel { }
    Component.onCompleted: console.log("created " + this + " with parent " + parent)
    Component.onDestruction: console.log("deleted " + this)
}
// Uimain.qml
Item {
    id: main
    width: childrenRect.width
    height: childrenRect.height
    property Item object
    property bool expanded : true
    Loader {
        id: li
        x: 50
        y: 50
        active: expanded && object && object.list.count
        width: childrenRect.width
        height: childrenRect.height
        sourceComponent: listView
    }
    Component {
        id: listView
        ListView {
            width: contentItem.childrenRect.width
            height: contentItem.childrenRect.height
            model: object.list
            delegate: Item {
                id: p
                width: childrenRect.width
                height: childrenRect.height
                Component.onCompleted: Qt.createComponent("Uimain.qml").createObject(p, {"object" : o})
            }
        }
    }
    Rectangle {
        width: 50
        height: 50
        color: "red"
        MouseArea {
            anchors.fill: parent
            acceptedButtons: Qt.RightButton | Qt.LeftButton
            onClicked: {
                if (mouse.button == Qt.RightButton) {
                    expanded = !expanded
                } else {
                    object.list.append({ "o" : Qt.createComponent("ObjMain.qml").createObject(object) })
                }
            }
        }
    }
}
// main.qml
Window {
    visible: true
    width: 1280
    height: 720
    ObjMain {
        id: obj
    }
    Uimain {
        object: obj
    }
}

这个例子是一个简单的对象树生成器,左按钮向节点添加一个叶子,右按钮折叠节点。重现错误所需要的只是创建一个深度为3的节点,然后折叠和扩展根节点,控制台输出显示:

qml: created ObjMain_QMLTYPE_0(0x1e15bb8) with parent QQuickRootItem(0x1e15ca8)
qml: created ObjMain_QMLTYPE_0(0x1e5afc8) with parent ObjMain_QMLTYPE_0(0x1e15bb8)
qml: created ObjMain_QMLTYPE_0(0x1e30f58) with parent ObjMain_QMLTYPE_0(0x1e5afc8)
qml: deleted ObjMain_QMLTYPE_0(0x1e30f58)

最深节点的object被无故删除,即使它是父节点Item的父节点,并且在列表模型中的JS对象中被引用。试图将新节点添加到最深的节点会使程序崩溃。

行为是一致的,无论树的结构如何,只有第二级节点存活,当树折叠时,所有更深的节点都会丢失。

故障不在于用作存储的列表模型,我已经用JS数组和QList进行了测试,但对象仍然丢失。本例使用列表模型只是为了保存C++模型的额外实现。到目前为止,我找到的唯一补救办法是完全拒绝QML对这些对象的所有权。尽管这个例子产生了相当一致的行为,但在生产代码中,自发的删除通常是完全任意的。

关于垃圾回收器——我以前测试过它,注意到它非常自由——创建和删除100 MB内存的对象并没有触发垃圾回收来释放内存,但在这种情况下,只有几个几百字节的对象被匆忙删除。

根据文档,具有父对象或被JS引用的对象不应被删除,在我的情况下,两者都是有效的:

该对象由JavaScript所有。当对象返回QML时作为方法调用的返回值,QML将跟踪并删除它如果没有剩余的JavaScript引用,并且没有Q对象::parent()

正如Filip的回答中所提到的,如果对象是由一个不在被删除对象中的函数创建的,则不会发生这种情况,因此这可能与模糊提及的与QML对象相关的JS状态有关,但我基本上仍然不知道为什么会发生删除,因此这个问题实际上仍然没有答案。

有什么原因吗?

更新:九个月后,这个关键错误的开发仍然为零。同时,我发现了一些其他场景,其中删除了仍在使用的对象,在这些场景中,对象是在哪里创建的并不重要,简单地在主qml文件中创建对象的解决方法也不适用。最奇怪的是,当对象被"取消引用"时,它们并没有被破坏,而是被"重新引用"时。也就是说,当引用它们的视觉对象被破坏时,它们并没有被破坏,而是在重新创建时。

好消息是,即使是在QML中创建的对象,也可以将所有权设置为C++,因此QML中对象创建的灵活性不会丢失。调用一个函数来保护和删除每个对象会带来一些小的不便,但至少可以避免QtQuick的错误寿命管理。尽管如此,我还是喜欢QML的"便利性"——被迫回到手动对象生命周期管理。

我曾多次遇到这个问题,对象是动态创建的,无论它们是在QML还是C++中创建的

只有当对象设置了JavaScriptOwnership时,才考虑对其进行垃圾收集,如果对象是,则情况就是这样

  1. 由JavaScript/QML直接创建
  2. 所有权明确设置为JavaScriptOwnership
  3. 该对象是从Q_INVOKABLE方法返回的,并且以前没有对其调用setObjectOwnership()

在所有其他情况下,对象都假定为C++所有,而不考虑进行垃圾收集。

起初,我认为这可能是养育子女的问题,因为我使用的是QObject派生的类,并且动态实例化的QML方法为父对象传递一个Item,而QtObject甚至没有父属性-它不是从QObject中公开的。

Qt对象树与Qml对象树完全不同。QML只关心它自己的对象树。

    delegate: Item {
        id: p
        width: childrenRect.width
        height: childrenRect.height
        Component.onCompleted: Qt.createComponent("Uimain.qml").createObject(p, {"object" : o})
    }

委托的onCompleted处理程序中动态创建的对象的组合必然会导致错误。

当您折叠树时,代理将被销毁,并随之销毁其所有子对象,其中包括动态创建的对象。如果仍然有关于孩子们的现场参考资料,那也没关系。

从本质上讲,您没有为树提供稳定的后备存储——它由一堆嵌套的委托组成,这些委托可以随时消失。

现在,在某些情况下,QML拥有的对象被意外删除:任何C++引用都不算作垃圾收集器的引用;这包括Q_PROPERTY。在这种情况下,您可以:

  1. 显式设置CppOwnership
  2. 使用QPointer<>保持引用以处理正在消失的对象
  3. 在QML中保持对对象的显式引用

QML在管理内存方面不是C++。QML的目的是负责分配内存和释放内存。我认为您发现的问题就是这个问题的结果。

如果动态对象创建过于深入,则所有内容似乎都会被删除。因此,您创建的对象是数据的一部分并不重要,它们也会被销毁。

不幸的是,我的知识到此为止。

解决这个问题的方法之一(证明了我之前的陈述)是将数据结构的创建从动态UI qml文件中移出:

  1. 例如,将对象创建函数放在main.qml中

function createNewObject(parentObject) {
    parentObject.list.append({ "o" : Qt.createComponent("ObjMain.qml").createObject(parentObject) })
}
  1. 在代码中使用此函数:

// fragment of the Uimain.qml file
    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.RightButton | Qt.LeftButton
        onClicked: {
            if (mouse.button == Qt.RightButton) {
                expanded = !expanded
            } else {
                createNewObject(object)
            }
        }
    }

在.js文件中创建一个数组,然后在该.js.文件的顶层创建一个具有var myArray = [];的数组实例。

现在,您可以引用附加到myArray的任何对象,包括动态创建的对象。

只要Javascript变量保持定义状态,它们就不会被垃圾收集删除,所以如果您将其定义为全局对象,然后将该Javascript文件包含在qml文档中,那么只要主qml在作用域中,它就会一直保留。


在一个名为:backend.js的文件中

var tiles = [];
function create_square(new_square) {
    var component = Qt.createComponent("qrc:///src_qml/src_game/Square.qml");
    var sq = component.createObject(background, { "backend" : new_square });
    sq.x = new_square.tile.x
    sq.y = new_square.tile.y
    sq.width = new_square.tile.width;
    sq.height = new_square.tile.height;
    tiles[game.board.getIndex(new_square.tile.row, new_square.tile.col)] = sq;
    sq.visible = true;
}

编辑

让我更清楚地解释一下这如何应用于您的特定树示例。

通过使用行property Item object,您无意中将其强制为Item的属性,QML中对此有不同的处理。具体来说,就垃圾回收而言,属性属于一组唯一的规则,因为QML引擎可以简单地开始删除任何对象的属性,以减少运行所需的内存。

相反,在QML文档的顶部,包括以下行:

import "./object_file.js" as object_file

然后在object_file.js文件中,包括以下行:

 var object_hash = []; 

现在,您可以随时使用object_hash来保存动态创建的组件,并通过引用来防止它们被删除

object_file.object_hash

对象。

无需疯狂更改所有权等