There are several questions on this subject that are unrelated to my question and They did not produce any results for me.
Imagine I have a splash screen with AnimatedImage in QML that I want to display when my heavy components are loading in the background, so I use a Loader to load assets in background, but when the loader starts loading my UI freezes(i.e. that AnimatedImage), I can see that BusyIndicator not freezes.
I have provided the full source code in the github repository so that you may test it more easily.
my questions are:
- Do Loaders really run in the background (for example, if I'm trying to connect to a server in my constructor, can Loader handle this situation or do I have to run it in another thread)?
- How should such scenarios be handled so that I do not see any glitches?
window.qml
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts
Window {
id:mainWindow
y:100
width: 640
height: 480
visible: true
flags: Qt.FramelessWindowHint
//splash screen
Popup {
id: popup
width: mainWindow.width
height: mainWindow.height
modal: false
visible: true
Overlay.modeless: Rectangle {
color: "#00000000"
}
//Splash loader
Loader{
id: splash
anchors.fill: parent
source: "qrc:/Splashscreen.qml"
}
}
// Timer that will start the loading heavyObjects
Timer {
id: timer
interval: 2000
repeat: false
running: true
onTriggered: {
loader.source = "qrc:/heavyObjects.qml"
loader.active = true
}
}
//write a loader to load main.qml
Loader {
id: loader
anchors.fill: parent
asynchronous: true
active: false
//when loader is ready, hide the splashscreen
onl oaded: {
popup.visible = false
}
visible: status == Loader.Ready
}
}
SplashScreen.qml
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
Item {
Rectangle {
id: splashRect
anchors.fill: parent
color: "white"
border.width: 0
border.color: "black"
AnimatedImage {
id: splash
source: "qrc:/images/Rotating_earth_(large).gif"
anchors.fill: parent
}
}
}
heavyObject.qml
import QtQuick
Item {
function cumsum() {
for(var j=0;j<100;j ){
var p = 0
for (var i = 0; i < 1000000; i ) {
p *= i
}
}
return ""
}
// show dummy text that this is the main windows
Text {
text: "Main Window" String(cumsum())
anchors.centerIn: parent
}
}
CodePudding user response:
Most things you do in QML are handled in the QML engine thread. If you do something heavy in that thread, it will block everything else. I haven't checked your source code, but, in terms of heavy initialization, we can break it up with Qt.callLater() or similar so that the QML engine thread can catch up on UI/UX events.
For example, in the following:
- I changed
cumsum
from a function to a property - I introduced
calcStep
for do a calculation for onej
iteration - I use
Qt.callLater
to instantiate the next iteration - I kick off the calculation during
Component.onCompleted
property string cumsum
function calcStep(j) {
if (j >= 100) {
cumsum = new Date();
return;
}
for (var i = 0; i < 1000000; i ) {
p *= i
}
Qt.callLater(calcStep, j 1);
}
Component.onCompleted: calcStep(0)
}
If your initialization is more sophisticated, you may want to give Promises a try. This allows you to write asynchronous routines in a synchronous type of way, e.g.
property string cumsum
function calc() {
_asyncToGenerator(function*() {
for(var j=0;j<100;j ){
var p = 0
status = "j: " j;
yield pass();
for (var i = 0; i < 1000000; i ) {
p *= i
}
}
cumsum = new Date();
})();
}
function pass() {
return new Promise(function (resolve, reject) {
Qt.callLater(resolve);
} );
}
Component.onCompleted: calc()
In the above, the cumsum
calculation has been using a derivative of the async/await pattern. For this, to work I make use of _asyncToGenerator
provided by a transpiler on babeljs.io. This is required since the QML/JS does not support async/await pattern until Qt6.6.
The pass()
function operates similarly to Python pass but has my implementation of Qt.callLater
wrapped in a Promise. Invoking it with yield pass();
does nothing but allows your function to momentarily release control so that the UI/UX events can catch up.
import QtQuick
import QtQuick.Controls
Page {
property string cumsum
property string status
// show dummy text that this is the main windows
Text {
text: "Main Window: " cumsum
anchors.centerIn: parent
}
Text {
text: status
anchors.horizontalCenter: parent.horizontalCenter
y: parent.height * 3 / 4
}
function calc() {
_asyncToGenerator(function*() {
for(var j=0;j<100;j ){
var p = 0
status = "j: " j;
yield pass();
for (var i = 0; i < 1000000; i ) {
p *= i
}
}
cumsum = new Date();
})();
}
function pass() {
return new Promise(function (resolve, reject) {
Qt.callLater(resolve);
} );
}
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args)
function _next(value) {
_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value)
}
function _throw(err) {
_asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err)
}
_next(undefined)
})
}
}
function _asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg)
var value = info.value
} catch (error) {
reject(error)
return
}
if (info.done) {
resolve(value)
} else {
Promise.resolve(value).then(_next, _throw)
}
}
Component.onCompleted: calc()
}
You can Try it Online!
If you are interested in some of the work I've done with async and QML Promises refer to the following GitHub projects: