Stop a QML application cleanly without references to repeating timers

1

The Question

I have a large application with many qml components. When I close my application I get a 0xC0000005 exit code. Below is a toy example which generates such an exit code as well. In it, I'm able to resolve the issue by stopping the ongoing timer before closing. I want to know how I'd do this if I didn't have a reference to the timer.

The Code

main.py

from PyQt5 import QtCore
from PyQt5.QtCore import qInstallMessageHandler
from PyQt5.QtCore import QObject
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine

import sys


def log(msg):
    print(msg)

def qt_message_handler(mode, context, message):
    modeMap = {QtCore.QtDebugMsg: 'DEBUG',
               QtCore.QtInfoMsg: 'INFO',
               QtCore.QtWarningMsg: "WARNING",
               QtCore.QtCriticalMsg: "CRITICAL",
               QtCore.QtFatalMsg: "FATAL"}
    logMsg = f"QT_{modeMap.get(mode, 'xxx')}: {message}"
    log(logMsg)

class Application(QObject):

    def __init__(self):
        super().__init__()

    def run(self):

        # set up
        qInstallMessageHandler(qt_message_handler)
        qml_src = QUrl.fromLocalFile('main.qml')

        app = QGuiApplication(sys.argv)
        engine = QQmlApplicationEngine()
        engine.load(qml_src)

        return app.exec_()


if __name__ == '__main__':
    Application().run()

main.qml

import QtQuick 2.0
import QtQuick.Layouts 1.11
import QtQuick.Controls 1.4

ApplicationWindow {
    id: root
    color: randomColor()

    width: 150
    height: 150

    visible: true

    Rectangle {
        anchors.fill: parent
        color: root.color
    }


    Timer {
      id: myTimer
      interval: 500; running: true; repeat: true;
      onTriggered: root.color = root.randomColor()
   }

   onClosing: {
      print("closing")
      // The following line stops the crash,
      // todo: How could I close cleanly if I don't have a reference 
      //       to deeply burried timers or other ongoing code?
      // myTimer.stop()
    }

   function randomColor() {
       var colors = ["red", "green", "blue",
                     "antiquewhite", "aqua", "aquamarine",
                     "blueviolet", "chartreuse", "cornflowerblue",
                     "deeppink", "darkorange", "indianred",
                     "mediumpurple", "plum", "moccasin",
                     "yellow", "silver", "tan"];

       var choice = Math.floor(Math.random()*colors.length)
       return colors[choice];
   }
}

python
pyqt
qml
pyqt5
asked on Stack Overflow Oct 30, 2019 by jcowfer • edited Oct 30, 2019 by eyllanesc

1 Answer

1

The problem is that you are eliminating the application from memory without other elements releasing the information, in this case "engine" is still freeing memory and for this you need the QXApplication.

Considering the above there are the following options:

  • Delete QQmlApplicationEngine before QXApplication is removed:
class Application(QObject):
    def run(self):

        # set up
        qInstallMessageHandler(qt_message_handler)
        qml_src = QUrl.fromLocalFile("main.qml")

        app = QGuiApplication(sys.argv)
        engine = QQmlApplicationEngine()
        engine.load(qml_src)

        ret = app.exec_()
        del engine
        # or
        # engine.deleteLater()
        return ret
  • Extend the life of QXApplication outside the run method using a global variable:
app = None


class Application(QObject):
    def run(self):

        # set up
        qInstallMessageHandler(qt_message_handler)
        qml_src = QUrl.fromLocalFile("main.qml")

        global app
        app = QGuiApplication(sys.argv)
        engine = QQmlApplicationEngine()
        engine.load(qml_src)

        return app.exec_()
answered on Stack Overflow Oct 30, 2019 by eyllanesc

User contributions licensed under CC BY-SA 3.0