Search code examples
pythonpysideqtquick2

Implementing QQuickAsyncImageProvider in Pyside; No such signal QObject::finished()


I'm attempting to implement a subclass of QQuickAsyncImageProvider in Pyside6, referring to the official example. I'm already aware of the potential hurdle that, unlike in the C++ example code, with PySide it's not possible to inherit from both QRunnable and QObject. In order to send a signal from my QRunnable I'm instead using an intermediary QObject, as suggested here. That part works fine.

Here's my attempt so far at using this arrangement along with my own subclass of QQuickAsyncImageProvider:

from PySide6.QtGui import QGuiApplication, QImage
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QObject, QRunnable, QThreadPool, Signal
from PySide6.QtQuick import (QQuickImageResponse, QQuickAsyncImageProvider,
                             QQuickTextureFactory)

class Signaller(QObject):
    done = Signal(QImage)


class AsyncImageResponseRunnable(QRunnable):
    def __init__(self):
        super().__init__()
        self.signaller = Signaller()
        self.done = self.signaller.done

    def run(self):
        image = QImage(400, 400, QImage.Format_RGB32)
        image.fill('red')
        self.done.emit(image)


class AsyncImageResponse(QQuickImageResponse):
    def __init__(self):
        super().__init__()

        runnable = AsyncImageResponseRunnable()
        runnable.done.connect(self.handle_done)
        pool = QThreadPool.globalInstance()
        pool.start(runnable)

    def handle_done(self, image):
        self.image = image
        self.finished.emit()
    
    def textureFactory(self):
        return QQuickTextureFactory.textureFactoryForImage(self.image)


class AsyncImageProvider(QQuickAsyncImageProvider):
    def requestImageResponse(self, image_id, requested_size):
        return AsyncImageResponse()


if __name__ == "__main__":
    app = QGuiApplication()
    engine = QQmlApplicationEngine()
    
    engine.addImageProvider('async_test', AsyncImageProvider())
    
    engine.load('test.qml')
    app.exec()
//test.qml

import QtQuick

Window {
    width: 1200
    height: 800
    visible: true

    Image {
        source: 'image://async_test/test'
    }
}

This causes a segmentation fault—and some of the time, immediately beforehand, I get this error message:

qt.core.qobject.connect: QObject::connect: No such signal QObject::finished()

At first glance, this seems like it would be related to the self.finished.emit() line. However:

  1. QQuickImageResponse defines the finished signal, and
  2. I get the same error message even when I comment out self.finished.emit(), which seems to indicate something internal must be attempting to call it.

So, what am I missing here? How do I avoid the segmentation fault and get my asynchronous image provider functional?


Solution

  • I reported this in case it was a bug, and one of the maintainers realized that the issue is that the AsyncImageResponse gets immediately deleted. The workaround for this is to hold a reference to it in the AsyncImageProvider:

    class AsyncImageProvider(QQuickAsyncImageProvider):
        def requestImageResponse(self, image_id, requested_size):
            self.response = AsyncImageResponse()
            return self.response
    

    They've also proposed a change that may make this unnecessary in the future.