Kurze Frage zu QThread -> moveToThread()

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

Hallo Zusammen,

ich habe ein einfaches Problem. Folgende Methode soll in diesem Beispiel 2 mal eine Instanz auf die Klasse "GraphicAlarmMachine erstellen. Leider funktioniert der Code nicht. Könnte mir jemand einen Hinweis geben, warum nicht? In der Liste self.workers habe ich 2 mal ein unterschiedliches Instanzobjekt gespeichert und in der Liste self.threads gibt es 2 QtCore.QThread Objekte :-) Die mag ich nun der Reihe nach starten, wie unten zu sehen. Das funktioniert so nur nicht. Bin wie immer für Hinweise sehr dankbar.

Code: Alles auswählen

    def clicked_startApplication(self):
        """ 
        create as much class references, as channelconfigs are available
        """
        self.connection = sqlite3.connect("ChannelConfiguration.db")
        self.cursor = self.connection.cursor()

       
        self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
   
        # create as much class references, as channelconfigs are available
        self.workers = []
        self.threads = []
        for i in range(len(self.cursor.fetchall())):
            self.workers.append(GraphicAlarmMachine())       
            self.threads.append(QtCore.QThread())
            self.workers[i].moveToThread(self.threads[i])
        
    
        for thread in self.threads:
            thread.start()
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@m.g.o.d: Bist Du sicher das da alles was Du an das Objekt bindest auch wirklich an das Objekt gebunden werden sollte? Mindestens mal der Cursor sollte das nicht.

Das ``for i in range(len(…)):`` ist ein „anti pattern“. Es ist sowieso unsinnig die Namen abzufragen nur um die Anzahl der Ergebnisse von der Länge der abgefragten Ergebnisse zu ermitteln, die Ergebnisse dann aber einfach wegzuwerfen. Wenn man nur die Anzahl wissen will, dann fragt man auch nur die Anzahl von der Datenbank ab.

Das `i` wird in der Schleife eigentlich gar nicht benötigt.

Das würde also eher so aussehen (`closing()` aus `contextlib`):

Code: Alles auswählen

    def clicked_startApplication(self):
        with closing(sqlite3.connect("ChannelConfiguration.db")) as connection:
            with closing(connection.cursor()) as cursor:
                cursor.execute(
                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table'"
                )
                count = cursor.fetchone()[0]

        self.workers = []
        self.threads = []
        for _ in range(count):
            worker = GraphicAlarmMachine()
            thread = QtCore.QThread()
            worker.moveToThread(thread)
            self.workers.append(worker)
            self.threads.append(thread)

        for thread in self.threads:
            thread.start()
Was erwartest Du denn was da jetzt passieren soll? Die Threads führen nichts aus. Was denkst Du denn was die auf magische Weise machen sollten und warum?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Diese for-Schleife ist ziemlich umständlich. Wenn Du die Anzahl wissen willst, benutze count.
i wird gar nicht gebraucht. `cursor` ist etwas kurzlebiges und sollte nicht an ein Attribut gebunden werden, ob das für die connection gilt sieht man hier nicht.

Code: Alles auswählen

    def clicked_startApplication(self):
        """ 
        create as much class references, as channelconfigs are available
        """
        self.connection = sqlite3.connect("ChannelConfiguration.db")
        cursor = self.connection.cursor()
        cursor.execute("SELECT count(*) FROM sqlite_master WHERE type='table';")
        amount = cursor.fetchone()[0]
   
        # create as much class references, as channelconfigs are available
        self.workers = []
        self.threads = []
        for _ in range(amount):
            machine = GraphicAlarmMachine()
            thread = QtCore.QThread()
            self.workers.append(machine)
            self.threads.append(thread)
            machine.moveToThread(thread)
            thread.start()
Was funktioniert denn nicht? Das kann ich an dem Code nicht erkennen.
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

Hallo blackjack, danke für den Hinweiß mit dem self.

naja, wenn ich thread.start() ausführe, erwarte ich ganz einfach, dass die Klasse "GraphicAlarmMachine" mehrmals gerufen wird und deren implementierte run(self) Methode mit dem darin stehenden Code ausgeführt wird. Magisch finde ich das eigentlich weniger.

Liefert count also 5 zurück, soll 5 mal die Klasse samt der Methode(n) gerufen werden. Würde ich jetzt 5 mal manuell jeweils eine Instanz auf die Klasse bilden, würde auch 5 mal das passieren, wass ich in der Klasse so machen möchte. Würde die run Methode einfach nur print("inside run") ausgeben, dann würde ich erwarten, dass es in unserem Beispiel 5 mal passiert, so wie wenn ich das manuell machen würde.
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

Hier nochmal der ganze Quellcode. Ziel ist, gemäß der Anzahl count "inside run" auszugeben:

Code: Alles auswählen

class GuiServerApplication(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = uic.loadUi('server_application.ui', self)
        self.ui.setWindowTitle('Server application')
        self.ui.startApplication.clicked.connect(self.clicked_startApplication)
        self.ui.client_connector.clicked.connect(self.clicked_client_conn)
        self.createLogging()

    def clicked_startApplication(self):
        """ 
        create as much class references, as channelconfigs are available
        """
        with closing(sqlite3.connect("ChannelConfiguration.db")) as connection:
            with closing(connection.cursor()) as cursor:
                cursor.execute(
                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table'"
                )
                count = cursor.fetchone()[0]

        self.workers = []
        self.threads = []
        for _ in range(count):
            worker = GraphicAlarmMachine()
            thread = QtCore.QThread()
            worker.moveToThread(thread)
            self.workers.append(worker)
            self.threads.append(thread)

        for thread in self.threads:
            thread.start()
            
           
class GraphicAlarmMachine(QThread):
    """ Core functionality:
        1. Grap Video, 2. Calculate Similarity, 3. Export Alarm
    """
    # Define and connect signals
    work_started = pyqtSignal()
    work_done = pyqtSignal()
    alarm_export = pyqtSignal(str)
    
    def run(self):
        self.work_started.emit()
        print("inside!") 
            
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@m.g.o.d: Da geht einiges durcheinander. Entweder Du verschiebst Objekte mit Slots in einen Thread der `run()` *nicht* implementiert, damit die Slots in dem Thread ausgeführt werden *oder* Du erbst von `QThread` und implementierst `run()`, dann brauchst Du aber natürlich nicht noch zusätzlich ein anderes `QThread`-Objekt.

Bevor Du Dir einen Threadpool selbst bastelst, möchtest Du vielleicht mal `QThreadPool` anschauen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

QThreadPool kenne ich von QRunnable. Das hätte ich auch gerne benutzt, das Problem für mich ist aber, dass ich auch ein Signal brauche, um Daten aus dem Thread in die Gui zu schicken...QRunnable unterstützt doch keine selbst definierten Signale, oder?
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

Habs jetzt doch hinbekommen! Danke für die ganzen hilfreichen Hinweise:
https://www.learnpyqt.com/tutorials/mul ... hreadpool/

Da steht beschrieben, wie QRunnable auch emit unterstützt :-)

Besten Dank
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

Hallo Zusammen,

nun experimentiere ich schon ein paar Tage mit QRunnable herum. Es scheint, ich kann in meinem Fall ledliglich 2 Threads starten:

Code: Alles auswählen

 print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
Eigentlich müsste ich wesentlich mehr threads parallel starten...ohne PyQT konnte ich das mit dem threading modul machen...aber das verträgt sich, soweit ich gehört habe, nicht mit dem QThread(Pool) von PyQT.

Wie würde ich mein Programm so umschreiben können, dass eine beliebige Anzahl von QRunnable Threads parallel aufgerufen werden könnten?

Besten Dank für eure Hinweise
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@m.g.o.d: Setz halt die Zahl hoch. Allerdings macht es keinen Sinn die beliebig hoch zu setzen, denn irgendwann kippt das ganze wenn sich zu viele Threads um Ressourcen kloppen. Und bei CPython muss man eh immer im Hinterkopf behalten das immer nur ein Thread Bytecode ausführen kann wegen dem „global interpreter lock“ (GIL).
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

Hi,

hab es gerade gefunden, über PySide.QtCore.QThreadPool.setMaxThreadCount(maxThreadCount) lässt sich die Zahl hochsetzen. Danke!

Wie kann ich mich bei der Threadzahl orientieren? Ich führe folgenden Thread aus und den würde ich gerne z.B. 50 mal mit jeweils anderen Parametern "quasi-parallel" aufrufen:

Code: Alles auswählen

@pyqtSlot()
    def run(self):
   
        # Loading source
        cap = cv2.VideoCapture(r'C:\Users\Marc\GAT_Graphic_Alarm_Tool\Demovideos\Untitled_03.wmv')
        
        # Loading Template
        template = cv2.imread(r'C:\Users\Marc\GAT_Graphic_Alarm_Tool\Testreihen\TLC_mainframe_template1.jpg', 0)

        frames = 0
        while True:
            success, img = cap.read() 

            # resize + Crop (ROI)
            imgCropped = img[self.y_axis:1080, self.x_axis:1920]
        
            # Convert Source to gray
            gray = cv2.cvtColor(imgCropped, cv2.COLOR_BGR2GRAY)

            height, width = template.shape[::]

            # cv2.TM_SQDIFF = Minimum Square Difference with Threshold
            res = cv2.matchTemplate(gray, template, cv2.TM_CCOEFF_NORMED)    
            
            #For TM_CCOEFF_NORMED, larger values means good fit
            threshold = 0.5

            # Return Tuple
            loc = np.where( res >= threshold)

            if len(loc[0]) > 0:
                print("match!")
            else:
                pass
                print("false")
im Grunde rechne ich hier nur mit Arrays und mache ein bisschen Video cropping...das würde jetzt mit dem QThreadpool ->setMaxThreadCount(50) keine gute Idee sein? Die while True Schleife wird später eh noch teilweise mit sleep(x) etwas "entschärft", um den Thread nicht zuzuballern.

Ohne PyQt5 habe ich es früher immer so gemacht:

Code: Alles auswählen

from concurrent import futures
from time import sleep, time
def test(t):
sleep(t)
print("Ich habe {} Sekunden gewartet. Zeit: {:.0f}".format(t, time()))
e = futures.ThreadPoolExecutor(max_workers=3)
print("Startzeit: {:.0f}".format(time()))
e.submit(test, 9)
e.submit(test, 2)
e.submit(test, 5)
e.submit(test, 6)
print("Alle Aufgaben gestartet.")
e.shutdown()
print("Alle Aufgaben erledigt.")
Hier dann die max_workers hochgesetzt. Oder gibt es da eine bessere Lösung?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ob das Sinn macht haengt davon ab, was die machen. CV ist ueblicherweise CPU-bound, also braucht CPU-Zylen. Und mehr Threads als Kerne die du hast fuehren nur dazu, dass die sich gegenseitig auf den Fuessen stehen. Was wahrscheinlich auch die Groesse des Thread-Pools begruendet: https://doc.qt.io/qt-5/qthreadpool.html ... Count-prop - per default auf die "ideaThreadCount" eingestellt.

Es bringt also genau gar nichts.
Antworten