Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QQuickImageProvider, async loading many images and threads.



  • Hello,
    Here are problem: 150+ elements with image in GridLayout (or GridView, not so important) and I want to implement caching/handling images before they come to QML via QQuickImageProvider (or QQuickAsyncImageProvider).

    Approach 1: Make provider class that inherits from QQuickImageProvider, implement requestImage (like in official example imageprovider-example ) only with loading images via QNetworkManager + handling (rounding image) + caching.
    Problem: If Image.asynchronous = false then image loading pretty slow and there are some visual laggs in qml (scene not redrawing while image loading); If Image.asynchronous = true then another 2 variant: 1.1 If NOT try to make method requestImage "reentrant" like written in docs then app crush; 1.2 If try to make MutexLocker in requestImage then no one image will load because loading get stucked on trying load first image (but app will work) right on string with eventLoop.exec():

    QImage MenuImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) {
      QMutexLocker locker(&mutex);
      QUrl url("path_to_image_in_internet" + id);    
      QNetworkAccessManager *manager = new QNetworkAccessManager;
      QNetworkReply* reply = manager->get(QNetworkRequest(url));
      QEventLoop eventLoop;
      QObject::connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
      eventLoop.exec(); // all get stucked here if using QMutexLocker locker(&mutex);
        if (reply->error() != QNetworkReply::NoError)
          return QImage();    
      image = QImage::fromData(reply->readAll());
      // .. + some handling and caching stuff
      size->setWidth(image.width());
      size->setHeight(image.height());
      return image;
    }
    

    Approach 2. Make provider class that inherits from QQuickAsyncImageProvider, implement requestImageResponse and so on like in official example imageresponseprovider-example ) also with loading images via QNetworkManager + handling + caching.
    Problem: after creating 150+ elements with images via Repeater + model I see how about 100 images successfully loads but after i got error in console output (but app still working):

    QThread::start: Failed to create thread (Недостаточно памяти для обработки команды.)
    

    translate from russian: Not enough memory for handle command
    It happend on Windows. If I launch this app on slow Android with 1 gb memory then app got crush with message about unable creating thread.

    And if I not using any ImageProvider, and use in Image.source url to internet it works correctly (with Image.asynchronous = true or false, not important). So how QML Engine by default handling this, and why that ImageProviders (from official docs) doesn't works? Where my mistake?

    There are implemetation of QQuickAsyncImageProvider:

    1. Base class for loading and handling images that inherits from QObject and QRunnable (like in docs)
    class BaseAsyncImageResponseRunnable : public QObject, public QRunnable
    {
        Q_OBJECT
    
    signals:
        void done(QImage image);
    
    public:
        BaseAsyncImageResponseRunnable(const QString &id, const QSize &requestedSize, QString imagesPath)
            : m_id(id), m_requestedSize(requestedSize), m_imagesPath(imagesPath) {}
    
        void run() override
        {
            QImage image;
            QUrl url(m_imagesPath + m_id);
    
            QNetworkAccessManager *manager = new QNetworkAccessManager;
            QNetworkReply* reply = manager->get(QNetworkRequest(url));
            QEventLoop eventLoop;
            QObject::connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
            eventLoop.exec();
            if (reply->error() != QNetworkReply::NoError) {
                emit done(image);
                return;
            }
            image = QImage::fromData(reply->readAll());
            image = handleImage(image);
            emit done(image);
        }
    
        virtual QImage handleImage(QImage image) { return image; }
    
    protected:
        QString m_id;
        QSize m_requestedSize;
        QString m_imagesPath;
    };
    
    1. Implement QQuickImageResponse in base class
    class BaseAsyncImageResponse : public QQuickImageResponse
    {
        public:
            BaseAsyncImageResponse(const QString &id, const QSize &requestedSize, QThreadPool *pool, BaseAsyncImageResponseRunnable *runnable)
            {
                connect(runnable, &BaseAsyncImageResponseRunnable::done, this, &BaseAsyncImageResponse::handleDone);
                pool->start(runnable);
            }
    
            void handleDone(QImage image) {
                m_image = image;
                emit finished();
            }
    
            QQuickTextureFactory *textureFactory() const override
            {
                return QQuickTextureFactory::textureFactoryForImage(m_image);
            }
    
            QImage m_image;
    };
    
    1. Implements QQuickAsyncImageProvider
    class MyAsyncImageProvider : public QQuickAsyncImageProvider
    {
    public:
        MyAsyncImageProvider() : QQuickAsyncImageProvider()
        { }
    
        QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override
        {
            BaseAsyncImageResponse *response;
            QString imagesPath = "pat_to_images_in_internet";
            auto runnable = new BaseAsyncImageResponseRunnable(id, requestedSize, imagesPath);
            response = new BaseAsyncImageResponse(id, requestedSize, &pool, runnable);
            return response;
        }
    
    private:
        QThreadPool pool;
    };
    

Log in to reply