Python/PySide limit refresh rate of Model/View

I have a Model like this:

class GeneralAssetIconModel(QtCore.QAbstractListModel):
    def __init__(self, parent=None):
        super(GeneralAssetIconModel, self).__init__(parent)
        self._data = []

    def rowCount(self, parent):
        return len(self._data)

    def data(self, index, role):
        if role == QtCore.Qt.DecorationRole:
            taskModel = self._data[index.row()]
            ext = taskModel.getData().obj['type']['ext']

            pix = QtGui.QPixmap(160, 160)
            pix.load('Assets/thumbnail-default.jpg')

            if ext == '.ma':
                pass
            if ext == '.psd':
                pix = PhotoshopHelper.getLatestThumbnail(taskModel)
            if ext == '.ai':
                pix = IllustratorHelper.getLatestThumbnail(taskModel)
            if ext == '.mra':
                pix = MariHelper.getLatestThumbnail(taskModel)
            if ext == '.indd':
                pix = IndesignHelper.getLatestThumbnail(taskModel)

The issue I’m having is that “getLatestThumbnail” function always reads thumbnail data from a server file and tries to display it in the view and this operation is quite slow. And when I have 30 or more items displayed in the list the whole thing becomes really slow and laggy.

Is there a way to limit the amount of times the view requests data from the model?

I have successfully optimized thumbnail loading by caching all the data in the model itself. Maybe it is not the best way to do this but it works really fast now. Here is what the model looks like now.

class GeneralAssetIconModel(QtCore.QAbstractListModel):
    def __init__(self, parent=None):
        super(GeneralAssetIconModel, self).__init__(parent)
        self._data = []
        self.cache = {}

    def rowCount(self, parent):
        return len(self._data)

    def data(self, index, role):
        index_row = index.row()
        if index_row in self.cache and 'DecorationRole' in self.cache[index_row] and 'DisplayRole' in self.cache[index_row]:
            if role == QtCore.Qt.DecorationRole:
                return self.cache[index_row]['DecorationRole']
            if role == QtCore.Qt.DisplayRole:
                return self.cache[index_row]['DisplayRole']
        else:
            if index_row not in self.cache:
                self.cache[index_row] = {}
            if role == QtCore.Qt.DecorationRole:
                taskModel = self._data[index_row]
                ext = taskModel.getData().obj['type']['ext']

                pix = QtGui.QPixmap(160, 160)
                pix.load('Assets/thumbnail-default.jpg')

                if ext == '.psd':
                    pix = PhotoshopHelper.getLatestThumbnail(taskModel)
                if ext == '.ai':
                    pix = IllustratorHelper.getLatestThumbnail(taskModel)
                if ext == '.mra':
                    pix = MariHelper.getLatestThumbnail(taskModel)
                if ext == '.indd':
                    pix = IndesignHelper.getLatestThumbnail(taskModel)
                if ext == '.ma':
                    pass

                self.cache[index_row]['DecorationRole'] = QtGui.QIcon(pix)
                return QtGui.QIcon(pix)
            if role == QtCore.Qt.DisplayRole:
                self.cache[index_row]['DisplayRole'] = self._data[index_row].getName()
                return self._data[index_row].getName()

I thought you were pre-loading all the images which sounds scary, but it looks like you’re just caching the same images and reusing them. What is your memory usage before and after? You may have accomplished data deduplication in addition to the load time improvements.

If you wanted to, you could probably pass a pointer ala PhotoshopHelper.getLatestThumbnail(taskModel, self.cache[index_row][‘DecorationRole’]) instead of using a return value (your pix variable) and make those routines deal with the threading, caching and such. They could update the entry to an already loaded image or temporarily point to a placeholder boilerplate “Loading…” image while they load the real thumbnail. You can make each .getLatestThumbnail() function share a common pool of file handles (say 4 or 8) so that you know you’re not going to hammer your network with a billion simultaneous load operations. You could also implement the same behavior here rather than in each .getLatestThumbnail() function.


while True:
    fileHandle = getFileHandle()  # <--manages a pool of file handles file handles
    if fileHandle:
        break
    time.Sleep(someInterval)

This could be important if you ever have to deal with really poor network latency (loading images over HTTP, remote users, etc.). Don’t forget to free your fileHandles.