Benchmark: Arbeiten im Thread

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Was ich eher hinderlich finde, ist, dass du in deinem Beispiel alles noch mehr verschachtelst und verkomplizierst, als meine Beispiele. In meinen Beispielen sind es nur zwei Klasse (Hauptthread und Worker) - fertig. Du arbeitest in deinem Beispiel mit drei Klassen (TimeMeasurer, Forwarder, BatchCounter). Dabei möchte ich doch nur ganz einfach aus dem Hauptthread, also aus der GUI heraus, dem Worker Aufträge liefern. Braucht es dafür gleich drei Klassen? Und genau dies finde ich kompliziert, weil ich hin und her springen muss, was macht diese Klasse, und wieso wird diese Klasse mit eingebunden und da ist ja noch eine. Ich nahm an, es gäbe eine strukturell einfachere Lösung, wie ich vom Hauptthread aus mit dem Worker in einer bi-dualen Beziehung arbeiten kann, ohne dass zwischen Hauptthread und Worker drei weitere Klassen dazwischen geschaltet werden müssen.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Man muss keine Batches zaehlen. Man muss auch keine Zeit messen. Zwei von unglaublich vielen drei (3!) Klassen koennen dementsprechend weg. Zweitens erreiche ich mit meinem einen Beispiel BEIDE use-cases darzustellen, unter Wiederverwendung von konsintent genutzen Klassen. Eine ehrliche Betrachtung wuerde also deine zwei Skripte meinem einen gegenueberstellen, und da sieht es mit dem Vergleich schon ganz anders aus.

Und der Forwarder ist nur ein Container fuer ein Signal. Ich haette auch ein QMeta-Objekt verwenden koennen, ob das deinem Verstaendnis zutraeglich gewesen waere wage ich zu bezweifeln. Wenn man aber nicht eine von diesen zwei Moeglichkeiten waehlt, dann erreicht man eben auch nicht das Ziel, sondern baut etwas wie du, das nicht funktioniert. So wie du, wenn du eigentlich Code im Hintergrundthread anwerfen willst, der dann aber im Main -Thread ausgefuehrt wird, da die fuer dieses Verhalten notwendige Signal/Slot-Verbindung fehlt.

Ich habe es schon mehrfach gesagt, hier, und in dem anderen Thread den du hier zu Beginn zitierst. Und womoeglich schon in der Vergangenheit, denn Hinweise auf Fehler in deinem threaded Code habe ich dir ja schon haeufiger mal gegeben: solange du nicht fundamental verstehst, wie QThreads, das Prinzip der Thread-Ownership von Objekten, und der Semantik von queued signal slot connections in Qt verstehst, wirst du dich weiterhin verheddern. Und so viel weniger als mein Code ist fuer ein solches Beispiel auch nicht moeglich. Nebenlaeufige Programmierung ist halt kompliziert.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Beziehen wir uns jetzt einmal auf meine wieder erneut eingestellten Beispiele. In der zweiten Variante liegt der Hund darin begraben, dass das start_order_signal ()-Signal im Hauptthread (MainThread()) ansiedelt, welcher zwar im Worker() mit entsprechender Methode verbunden ist, jedoch im Hauptthread emittiert wird. Und diese Verbindung zieht den Prozess in den Hauptthread?

UPDATE:

Ich habe dein Beispiel hinzugezogen und deine Forwarder()-Klasse mit eingebunden. Nachfolgend zeige ich das überarbeitete Beispiel. Ich hoffe, dass ich es richtig verstanden und umgesetzt habe. Die Zeitmessung liegt bei: Duration: 0:01:03.155000. Irgendwie habe Gefühl, dass mit der started() das befüllen des TreeWidgets schneller ist, denn ich bleibe dabei immer unter einer Minute.

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
import time
from datetime import datetime

from PyQt4.QtCore import QTimer, QObject, pyqtSignal, \
                         QThread, Qt

from PyQt4.QtGui import (QApplication, QDialog, QLabel, \
                         QTreeWidget, QTreeWidgetItem, \
                         QPushButton, QVBoxLayout)

class Forwarder(QObject):
    """
    Just a holder for one signal
    """
    enqueue_work = pyqtSignal()
    
    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        
class Worker(QObject):
    
    generated_item = pyqtSignal(str)
    count_item = pyqtSignal(int)
    
    def __init__(self, parent=None):
        QObject.__init__(self, parent)

        self.worker_generator = self.generate_work()

        # start to stopp the time
        self.start_time = datetime.now()

        self.work_timer = QTimer()
        self.work_timer.setSingleShot(False)
        self.work_timer.setInterval(1)
        self.work_timer.timeout.connect(self.populate_item)

    def start_work_timer(self):
        self.work_timer.start()
    
    def generate_work(self,
                      tasknumber=10000):

        count = 0
               
        for name in xrange(tasknumber):

            count += 1
            self.count_item.emit(count)
            
            yield str(name)

    def populate_item(self):

        try:
            self.generated_item.emit(next(self.worker_generator))

        except StopIteration:

            self.work_timer.stop()

            # end with to stopp the time
            self.end_time = datetime.now()

            # Show how much time has been elapsed.
            print('Duration: {}'.format(self.end_time - self.start_time))
        
class MainThread(QDialog):

    def __init__(self,
                 parent = None):
        QDialog.__init__(self, parent)

        self.setWindowTitle('ThreadTest')

        self.generate_work_load_thread()

        layout = QVBoxLayout(self)

        self.tree = QTreeWidget(self)
        self.counting_label = QLabel(self)
        
        self.create_worker_thread_button = QPushButton(self)
        self.create_worker_thread_button.setText("Create Worker Thread and start work order")
        self.create_worker_thread_button.clicked.connect(self.forwarder.enqueue_work.emit)
        
        self.close_button = QPushButton(self)
        self.close_button.setText("Close")
        self.close_button.clicked.connect(self.close)
   
        layout.addWidget(self.counting_label)
        layout.addWidget(self.tree)
        layout.addWidget(self.create_worker_thread_button)
        layout.addWidget(self.close_button)

    def generate_work_load_thread(self):

        self.forwarder = Forwarder()

        self.task_thread = QThread()

        self.task_thread.work = Worker(self)
        self.task_thread.work.moveToThread(self.task_thread)
        
        self.task_thread.work.generated_item.connect(self.add_item)
        self.task_thread.work.count_item.connect(self.count_population)

        self.forwarder.enqueue_work.connect(self.task_thread.work.start_work_timer)
        
        self.task_thread.finished.connect(self.task_thread.deleteLater)

        self.task_thread.start()

    def count_population(self,
                         count):

        self.counting_label.setText("Item(s): {}".format(count))
    
    def add_item(self, text):
        
        parent = QTreeWidgetItem(self.tree)
        self.tree.addTopLevelItem(parent)
        parent.setText(0, text)
        
def main():

    app = QApplication(sys.argv)

    w = MainThread()
    w.show()

    app.exec_()
    sys.exit()

if __name__ == '__main__':
    main()
Zuletzt geändert von Sophus am Montag 19. November 2018, 17:29, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich beziehe mich nicht auf deine Beispiele. Mein Beispiel funktioniert. Erarbeite dir das Verstaendnis dafuer. Dann kannst du es fuer dich nutzen. Oder lass es sein. Fuer mich ist EOD.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und um das noch mal klar zu machen: der Forwarder ist nur ein Signal. Ich haette auf den auch verzichten koennen faellt mir gerade auf, weil ich einfach das Button-clicked auf sowohl den counter.inc als auch den Worker haette connecten koennen. Das ist also der irrelevanteste Teil des Codes. Es gebt um ein Signal, das einen Slot im anderen Thread aufruft.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Also doch "überzogen"? :)
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Gottlob machst du ja keine Fehler. Da ist jede Zeile zwingend notwendig. Zb einen zweiten counter von 0 bis tasknumber, weil's so schoen ist...
Antworten