Мне нужно запрограммировать табличное представление, которое должно обрабатывать десятки тысяч ячеек, содержащих изображения (изображение отличается для каждой ячейки).Все в 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_()