QTableView - Как обрабатывать много сигналов dataChanged? - PullRequest
0 голосов
/ 18 октября 2018

Мне нужно запрограммировать табличное представление, которое должно обрабатывать десятки тысяч ячеек, содержащих изображения (изображение отличается для каждой ячейки).Все в python с использованием PySide2.

Я реализовал загрузку своих изображений с помощью пула потоков.Проблема в том, что я должен асинхронно уведомлять представление о том, что изображение было загружено для данного индекса, чтобы он мог перезагрузить дисплей.Использование сигнала dataChanged работает, но их слишком много для обработки, и пользовательский интерфейс не отображается, пока все индексы не обработаны пулом потоков.

Я приведу рабочий примерниже, который воспроизводит проблему (без изображений, текст для выравнивания).Пока что я решил проблему, позволив потокам немного поспать (просто раскомментируйте строку time.sleep (1) в методе Work.run), но это больше похоже на грязный хак, чем на реальное решение для меня.

Я подумал о следующих решениях:

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

У вас есть какие-либо другие идеи о том, как избежать такого поведения блокировки?

Спасибо за совет!

import math
from random import choice
import string
import time

from PySide2.QtCore import QModelIndex
from PySide2.QtCore import Qt
from PySide2.QtCore import QObject
from PySide2.QtCore import Signal
from PySide2.QtCore import QThread
from PySide2.QtCore import QThreadPool
from PySide2.QtCore import QMutex
from PySide2.QtCore import QAbstractTableModel
from PySide2.QtWidgets import QTableView


class Notifier(QObject):

    finished = Signal(QModelIndex, str)


class Work(QThread):

    def __init__(self, *args, **kwargs):
        super(Work, self).__init__(*args, **kwargs)
        self.work = []
        self._stopped = False
        self._slept = False

    def run(self):

        while True:

            try:
                work = self.work.pop(0)
            except IndexError:
                work = None

            if not work:
                if self._slept:
                    break

                self.msleep(500)
                self._slept = True
                continue

            # Uncomment the following line to make the UI responsive
            # time.sleep(1)

            if work[0]:
                c = ''.join(choice(string.ascii_uppercase + string.digits)
                            for _ in range(6))
                work[0].finished.emit(work[1], c)

    def reset(self):
        self.work = []
        self._stopped = True


class WorkPool(object):

    def __init__(self):

        self.thread_count = QThreadPool().maxThreadCount()
        self.thread_pool = []
        self.thread_cpt = 0
        self.mutex = QMutex()

        for c in range(0, self.thread_count):
            self.thread_pool.append(Work())

    def add_work(self, notifier, index):

        new_thread = divmod(self.thread_cpt, self.thread_count)[1]
        thread = self.thread_pool[new_thread]
        self.thread_cpt += 1

        thread.work.append((notifier, index))

        if not thread.isRunning():
            thread.start()

    def terminate(self):
        self.mutex.lock()

        for t in self.thread_pool:
            t.reset()

        for t in self.thread_pool:
            t.wait()

        self.mutex.unlock()


class TableModel(QAbstractTableModel):

    def __init__(self, items, *args, **kwargs):
        super(TableModel, self).__init__(*args, **kwargs)

        self.items = items
        self.works = []
        self.loader = WorkPool()

    def index(self, row, column, parent=QModelIndex()):

        pos = row * self.columnCount() + column

        try:
            return self.createIndex(row, column,self.items[pos])

        except IndexError:
            return QModelIndex()


    def data(self, index, role):

        if not index.isValid():
            return None

        if role == Qt.DisplayRole:
            return index.internalPointer()

    def columnCount(self, parent=QModelIndex()):
        return 10

    def rowCount(self, parent=QModelIndex()):
        return int(math.ceil(float(len(self.items)) / self.columnCount()))

    def refresh_content(self):

        # Launch a thread to update the content of each index
        for r in range(0, self.rowCount()):
            for c in range(0, self.columnCount()):
                index = self.index(r, c)

                notifier = Notifier()
                notifier.finished.connect(self.setData)

                self.loader.add_work(notifier, index)

    def setData(self, index, value):

        if not index.isValid():
            return False

        self.items[index.row() * self.columnCount() + index.column()] = value
        self.dataChanged.emit(index, index)
        return True



class TableView(QTableView):

    def closeEvent(self, *args, **kwargs):
        self.model().loader.terminate()
        super(TableView, self).closeEvent(*args, **kwargs)


if __name__ == '__main__':

    from PySide2.QtWidgets import QApplication

    app = QApplication([])

    tv = TableView()

    model = TableModel([None] * 99999)
    tv.setModel(model)
    model.refresh_content()

    tv.show()

    app.exec_()
...