Очередь доступа в QThread
Задача простая - создаем сцену, на нее добавляем объекты (~400к+шт).
Объекты должны изменять свои параметры в режиме реального времени, но делать расчеты новых параметров в основном потоке никак нельзя, так как сцена будет тормозить.
Расчеты нужно производить в другом потоке (желательно каждые 100мс) и там же обновлять параметры объектов, а основной поток сцены должен продолжать рисовать объекты.
Код:
class CalcThread(QThread):
def __init__(self, parent=None, scene: QGraphicsScene = None):
QThread.__init__(self, parent)
self.scene = scene
def run(self):
while True:
# Долгие расчеты новых параметров для объектов сцены..
self.scene.update(self.scene.views()[0].mapToScene(self.scene.views()[0].rect()).boundingRect())
print(self.scene.views()[0].rect(), len(self.scene.items()))
time.sleep(1)
class Widget_GraphSurface(QWidget):
def __init__(self):
super().__init__()
self.mainScene = QGraphicsScene()
self.mainScene.addItem(добавляем много объектов....)
self.ggv_main = QGraphicsView(self.mainScene)
mThread = QThread()
cThread = CalcThread(None, self.mainScene)
cThread.moveToThread(mThread)
cThread.start()
Если запустить в таком виде, вроде бы в методе run, потока CalcThread, все работает как нужно, а именно - он блокирует доступ к mainScene основному потоку, пока проводит расчеты. Но сцена начинает сильно тормозить, вроде все логично, основной поток не может получить self.mainscene пока она занята в CalcThread. Я так полагаю, что управление self.mainscene, возвращается после каждой завершенной итерации метода run из CalcThread.
Задача все же состоит в том, чтобы сделать незаметными расчеты, чтобы сцена продолжала плавно рисовать элементы.
Были предприняты попытки:
- Делать расчеты в обычном потоке
threading.Thread- все летало, до того момента, как поток изthreading.ThreadиMainThread(основной поток сцены), пытались получить доступ к сцене одновременно - получаем аксес виолайшн - Метод 1 + использование
Lock,RLock, семафоры и т.д. - итог один аксес виолейшн - Передача в поток
CalcThreadнапрямую всего экземпляра класса со сценой и вызов из него функции расчетов - итог - аксес виолешн - Метод 3 + кьютишные мютексы и локи - аксес виолейшн
Итого: лучше всего на данный момент работает, если расчеты делать в том же потоке, где и сцена, но картинка подтормаживает, а хочется, наоборот. Возникает несколько вопросов:
- Я не правильно использую
QThread, что нужно исправить? - Может нужно использовать кьютишные мютексы в потоке
CalcThread? - Может нужно организовать очередь доступа к объекту сцены?
- Нужен другой подход к этому вопросу?
По сути мне нужно:
- В потоке
CalcThreadполучить сцену - Получить некоторые объекты со сцены
- Получить параметры сцены и этих объектов
- Отдать управление сценой обратно в
MainThread - Произвести расчеты
- Снова получить сцену в потоке
CalcThread - Изменить параметры объектов на сцене
- Отдать сцену обратно в
MainThread
Ответы (1 шт):
Я не уверен, что правильно понимаю то, что вы хотите сделать. Но мне кажется, что это может выглядеть примерно так:
import sys
import random
from PyQt5.Qt import *
r = lambda : random.randint(0, 255)
r255 = lambda : (r(), r(), r())
class Scene(QGraphicsScene):
def __init__(self):
super().__init__()
# (добавляем много объектов....)
for i in range(400):
item = QGraphicsEllipseItem()
item.setRect(0, 0, r(), r())
item.setBrush(QColor(*r255()))
item.setPos(r()*5, r()*5)
self.addItem(item)
class CalcThread(QThread):
change_r = pyqtSignal(int, object)
def __init__(self, _items, parent=None):
super(CalcThread, self).__init__(parent)
self._items = _items
def run(self):
while True:
# Долгие расчеты новых параметров для объектов сцены..
for i in range(1, self._items):
r = lambda : random.randint(0, 255)
self.change_r.emit(i, r)
self.msleep(100)
class Widget_GraphSurface(QWidget):
def __init__(self):
super().__init__()
self.resize(1000, 600)
self.mainScene = Scene()
pixmap = QPixmap("hand128.png")
pixmap_item = QGraphicsPixmapItem(pixmap)
pixmap_item.setFlags(
pixmap_item.flags()
| QGraphicsItem.ItemIsSelectable
| QGraphicsItem.ItemIsMovable
)
pixmap_item.setPos(700, 700)
self.mainScene.addItem(pixmap_item)
self.ggv_main = QGraphicsView(self.mainScene)
layout = QGridLayout(self)
layout.addWidget(self.ggv_main, 0, 0)
_items = len(self.mainScene.items())
self.mThread = CalcThread(_items)
self.mThread.change_r.connect(self.change_item)
self.mThread.start()
def change_item(self, i, r):
item = self.mainScene.items()[i]
item.setRect(0, 0, r(), r())
item.setPos(r()*7, r()*7)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Widget_GraphSurface()
w.show()
sys.exit(app.exec_())
hand128.png

