В QML загрузчик зависает в пользовательском интерфейсе при загрузке больших/требующих много времени объектов

Есть несколько вопросов на эту тему, которые не связаны с моим вопросом и они не дали мне никаких результатов.

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

Я предоставил полный исходный код в репозитории github, чтобы вам было легче его протестировать.

мои вопросы:

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

окно.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
        onLoaded: {
            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
    }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
114
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Большинство вещей, которые вы делаете в QML, обрабатываются в потоке движка QML. Если вы сделаете что-то тяжелое в этом потоке, он заблокирует все остальное. Я не проверял ваш исходный код, но с точки зрения тяжелой инициализации мы можем разбить его с помощью Qt.callLater() или аналогичного, чтобы поток механизма QML мог наверстать упущенное в событиях UI/UX.

Например, в следующем:

  • Я изменил cumsum с функции на свойство
  • Я ввел calcStep для расчета за одну j итерацию
  • Я использую Qt.callLater для создания следующей итерации
  • Я запускаю расчет во время 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)
}

Если ваша инициализация более сложная, вы можете попробовать Promises. Это позволяет вам писать асинхронные подпрограммы синхронным способом, например.

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

В приведенном выше расчете cumsum использовалась производная от шаблона async/await. Для этого я использую _asyncToGenerator, предоставленный транспайлером на babeljs.io. Это необходимо, поскольку QML/JS не поддерживает шаблон async/await до версии Qt6.6.

Функция pass() работает аналогично передаче Python, но моя реализация Qt.callLater заключена в промис. Вызов его с помощью yield pass(); ничего не делает, но позволяет вашей функции на мгновение отпустить управление, чтобы события UI/UX могли наверстать упущенное.

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

Вы можете Попробовать онлайн!

Если вам интересна часть работы, которую я проделал с асинхронностью и обещаниями QML, обратитесь к следующим проектам GitHub:

Хотя это не то, что я имел в виду, ваши предложения продуманы и высоко ценятся. Я работаю с MCU, который не очень находчив. Как я тестировал, мой экран-заставка зависает при загрузке большого изображения, и я хотел бы исправить эту проблему. Этот фиктивный цикл for можно рассматривать как имитацию этого большого изображения, мне не нравится видеть это зависание на моем экране-заставке, у вас есть какие-либо идеи для этого? Спасибо

Saeed Masoomi 06.11.2022 11:12

@SaeedMasoomi хорошо, я посмотрел ваш репозиторий на GitHub. Анимационный gif маленький и не является причиной проблемы. Единственная причина, по которой анимация зависает, заключается в том, что HeavyObject.qml также работает в том же потоке QML Engine, поэтому он блокирует пользовательский интерфейс. Это, как я упоминал ранее. Вы должны дать нам некоторое представление о том, что делает HeavyObject.qml, чтобы мы могли видеть, есть ли способы, которыми мы можем заставить его отступить от потока механизма QML. Если нет, то такие элементы UI/UX, как щелчки мышью, анимированные GIF-файлы и т. д., будут заблокированы.

Stephen Quan 06.11.2022 22:15

Предоставленную вами информацию трудно получить в таком структурированном виде, поэтому спасибо за любезную помощь и время, которое вы уделили.

Saeed Masoomi 07.11.2022 10:54

Другие вопросы по теме