QThread klemmt!?
Was Dein Stopp gerade macht:
Beim ersten Start wird `stopp()` vor `moveToThread` gebunden, ergo beim Stoppen im Mainthread aufgerufen. Daher funktioniert es einmal.
Beim zweiten Mal ist das Objekt bereits in einem anderen Thread, was `.connect` veranlasst, eine queued connection zu platzieren. Da aber im Subthread kein Eventloop läuft, kommt da nix an.
Damit das korrekt funktioniert, musst Du Dein WorkObject auf ereignisgesteuert umstellen, sprich die run-sleep-Logik loswerden, alles über den threadlokalen Eventloop steuern (z.B. per QTimer) und den Loop auch starten (per `exec_`).
NB: Auch sollte man Threads korrekt beenden, was bei bestehenden threadübergreifenden Signal-Slot-Verbindungen mit `deleteLater` gemacht werden muss, damit pending events nicht einen hängenden Zeiger triggern.
Beim ersten Start wird `stopp()` vor `moveToThread` gebunden, ergo beim Stoppen im Mainthread aufgerufen. Daher funktioniert es einmal.
Beim zweiten Mal ist das Objekt bereits in einem anderen Thread, was `.connect` veranlasst, eine queued connection zu platzieren. Da aber im Subthread kein Eventloop läuft, kommt da nix an.
Damit das korrekt funktioniert, musst Du Dein WorkObject auf ereignisgesteuert umstellen, sprich die run-sleep-Logik loswerden, alles über den threadlokalen Eventloop steuern (z.B. per QTimer) und den Loop auch starten (per `exec_`).
NB: Auch sollte man Threads korrekt beenden, was bei bestehenden threadübergreifenden Signal-Slot-Verbindungen mit `deleteLater` gemacht werden muss, damit pending events nicht einen hängenden Zeiger triggern.
@jerch, erst einmal vielen Dank.
Ich glaube, ich habe alles verschlimmbessert. Hier mein Beispiel-Quelltext.
Was habe ich getan? Zunächst einmal habe ich das Timer-Modul entfernt. Dann ging ich dazu über in Zeile 29 die »run()«-Methode der »WorkObject()«-Klasse umzugestalten. In dieser besagten Methode erzeuge ich den QTimer(). In Zeile 36 stelle ich eine Verbindung zur »add_items()«-Funktion her. Da ich nicht will, dass der QTimer nur einmal feuert, habe ich in Zeile 35 den QTimer() mittels der »setSingleShot()«-Methode auf False gesetzt. Der Timer soll sich in einem Interval mit der Funktion verbinden und feuern. In der »stop()«-Methode der »WorkObject()«-Klasse will ich den QTimer() stoppen. Des Weiteren habe ich in Zeile 88 die »deleteLater()«-Methode der »QThread()«-Klasse angewendet. Zuvor hatte ich die »quit()«-Methode, anstatt »deleteLater()«-Methode angewendet - in der Hoffnung, dass durch die »quit()«-Methode der die »Qthread()«-Klasse der besagte Thread auch wirklich beendet wird.
Verschlimmbessert wurde mein Beispiel in dem Fall, weil der Vorgang mächtig ruckelt, und der Stopp gar nicht reagiert.
Ich glaube, ich habe alles verschlimmbessert. Hier mein Beispiel-Quelltext.
Code: Alles auswählen
import sys
from PyQt4.QtCore import QThread, pyqtSignal, Qt, QStringList, QObject, QTimer
from PyQt4.QtGui import QVBoxLayout, QPushButton, QDialog, QProgressBar, QApplication, \
QMessageBox, QTreeWidget, QTreeWidgetItem, QLabel
def create_items(total):
for elem in range(total):
yield elem
class WorkObject(QObject):
notify_progress = pyqtSignal(object)
fire_label = pyqtSignal(object)
finished = pyqtSignal()
def __init__(self):
QObject.__init__(self)
def add_items(self):
total = 190000
count = 0
for i in create_items(total):
self.notify_progress.emit((i))
count += 1
self.fire_label.emit((count))
def run(self):
self.timer = QTimer()
# I set the single shot timer on False,
# because I don't want the timer to fires only once,
# it should fires every interval milliseconds
self.timer.setSingleShot(False)
self.timer.timeout.connect(self.add_items)
self.timer.start(500)
def stop(self):
self.timer.stop()
class MyCustomDialog(QDialog):
finish = pyqtSignal()
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self.work_object = WorkObject()
layout = QVBoxLayout(self)
self.tree = QTreeWidget(self)
self.label = QLabel(self)
self.pushButton_start = QPushButton("Start", self)
self.pushButton_stopp = QPushButton("Stopp", self)
self.pushButton_close = QPushButton("Close", self)
layout.addWidget(self.label)
layout.addWidget(self.tree)
layout.addWidget(self.pushButton_start)
layout.addWidget(self.pushButton_stopp)
layout.addWidget(self.pushButton_close)
self.pushButton_start.clicked.connect(self.on_start)
self.pushButton_stopp.clicked.connect(self.on_finish)
self.pushButton_close.clicked.connect(self.close)
def fill_tree_widget(self, i):
parent = QTreeWidgetItem(self.tree)
self.tree.addTopLevelItem(parent)
parent.setText(0, unicode(i))
parent.setCheckState(0, Qt.Unchecked)
parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
def on_label(self, i):
self.label.setText("Result: {}".format(i))
def on_start(self):
self.tree.clear()
self.label.clear()
task_thread = QThread(self)
self.work_object.fire_label.connect(self.on_label)
self.work_object.notify_progress.connect(self.fill_tree_widget)
self.work_object.finished.connect(task_thread.deleteLater)
self.finish.connect(self.work_object.stop)
self.work_object.moveToThread(task_thread)
task_thread.started.connect(self.work_object.run)
task_thread.start()
def on_finish(self):
self.finish.emit()
def main():
app = QApplication(sys.argv)
window = MyCustomDialog()
window.resize(600, 400)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Verschlimmbessert wurde mein Beispiel in dem Fall, weil der Vorgang mächtig ruckelt, und der Stopp gar nicht reagiert.
@Sophus:
Die Sache mit dem QTimer ist schon die richtige Idee, um die Threadarbeit auf Ereignissteuerung umzustellen. Nur hast Du letzteres nicht gemacht, sondern einfach alle Arbeit in `add_items` reingeklatscht und lässt das Ganze alle 500ms wiederholen. Du musst schon nachvollziehen, was Dein Code eigentlich macht und die Umformung richtig vornehmen. Als Tipp dazu - überleg mal, wie Du eine simple Endlos-Zählschleife unter Nutzung von QTimer umstellen würdest.
Damit der Hauptthread responsiv bleibt, musst Du u.a. die Ereignislast reduzieren, sonst ist auch der Hauptthread zu 99% mit der Abarbeitung der Ereignisse beschäftigt. Und damit der Subthread sofort beginnt, solltest Du den Timer auf 0 setzen.
Auch ist das Verschieben des dialogweiten `worker_object` in ein neues Thread-Exemplar in `on_start` mehr als ungünstig und funktioniert auch beim 2. Mal nicht mehr (`moveToThread` ist selbst nicht threadsafe und daher nur für threadlokale Objekte erlaubt).
Leider fehlen PyQt die QtConcurrent- und Future-Implementationen für simple nebenläufige Aktionen, da diese templatisiert in C++ sind und sich nicht in prototypisieren lassen. Man kann sich aber selbst weiterhelfen, hier mal als Idee:
Die Sache mit dem QTimer ist schon die richtige Idee, um die Threadarbeit auf Ereignissteuerung umzustellen. Nur hast Du letzteres nicht gemacht, sondern einfach alle Arbeit in `add_items` reingeklatscht und lässt das Ganze alle 500ms wiederholen. Du musst schon nachvollziehen, was Dein Code eigentlich macht und die Umformung richtig vornehmen. Als Tipp dazu - überleg mal, wie Du eine simple Endlos-Zählschleife unter Nutzung von QTimer umstellen würdest.
Damit der Hauptthread responsiv bleibt, musst Du u.a. die Ereignislast reduzieren, sonst ist auch der Hauptthread zu 99% mit der Abarbeitung der Ereignisse beschäftigt. Und damit der Subthread sofort beginnt, solltest Du den Timer auf 0 setzen.
Auch ist das Verschieben des dialogweiten `worker_object` in ein neues Thread-Exemplar in `on_start` mehr als ungünstig und funktioniert auch beim 2. Mal nicht mehr (`moveToThread` ist selbst nicht threadsafe und daher nur für threadlokale Objekte erlaubt).
Leider fehlen PyQt die QtConcurrent- und Future-Implementationen für simple nebenläufige Aktionen, da diese templatisiert in C++ sind und sich nicht in prototypisieren lassen. Man kann sich aber selbst weiterhelfen, hier mal als Idee:
Code: Alles auswählen
from time import sleep
from PyQt4.QtCore import QThread, QObject, QTimer, pyqtSignal, QCoreApplication
class TaskThread(QThread):
'''
Thread container for a Task.
Overrides `run` to initialize and run a single task object.
'''
def __init__(self, obj, args, kwargs, parent=None):
QThread.__init__(self, parent)
obj.moveToThread(self)
self.obj = obj
self.args = args
self.kwargs = kwargs
def run(self):
try:
self.obj.init(*self.args, **self.kwargs)
except:
pass
self.obj.result = self.obj.run()
QCoreApplication.processEvents()
def _close(self):
self.quit()
self.wait()
self.obj.thread = None
self.obj.deleteLater()
self.deleteLater()
class Task(QObject):
'''
Task - a `run` method based self containing theaded task.
Executes the `init` and `run` method within a subthread.
The provided `processEvents` method should be called once in a while
in `run` to be able to catch events and trigger slots.
Any result of `run` will be emitted via the `finished` signal.
'''
finished = pyqtSignal(object)
def __init__(self, *args, **kwargs):
QObject.__init__(self, None)
self.result = None
self.thread = TaskThread(self, args, kwargs, None)
self.thread.finished.connect(self.thread._close)
self.thread.finished.connect(self._finished)
def processEvents(self):
QCoreApplication.processEvents()
def start(self):
self.thread.start()
def _finished(self):
self.finished.emit(self.result)
class SimpleRunner(Task):
'''
Container to run functions threaded.
'''
def init(self, func, fargs, fkwargs):
self.func = func
self.fargs = fargs
self.fkwargs = fkwargs
def run(self):
return self.func(*self.fargs, **self.fkwargs)
def to_be_threaded():
workload = ['puuh', 'this is hard to compute', '...', 'we are', 'almost there', '... done']
for s in workload:
print 'simple runner:', s
sleep(1)
return 42
class TaskWithSignalsAndSlots(Task):
'''
Task example with signal and slot dispatching.
'''
counter = pyqtSignal(int)
def init(self):
self.keepRunning = True
def run(self):
counter = 0
while self.keepRunning:
counter += 1
self.counter.emit(counter)
sleep(1)
self.processEvents()
def stop(self):
print 'slot stop() triggered'
self.keepRunning = False
def simple_result(result):
print 'result of the the long awaited calculation:', result
def outputCounter(counter):
print 'signal counter(int) from task', counter
if __name__ == '__main__':
app = QCoreApplication([])
# simple threaded functions
simple = SimpleRunner(to_be_threaded, [], {})
simple.finished.connect(simple_result)
simple.start()
# more sophisticated example with signals and slots
task = TaskWithSignalsAndSlots()
task.counter.connect(outputCounter)
task.finished.connect(QCoreApplication.instance().quit)
timer = QTimer()
timer.setInterval(7000)
timer.timeout.connect(task.stop)
timer.start()
task.start()
app.exec_()
@jerch:
Besten Dank. Ich habe deine Ausführung mal als Blaupause genommen und versucht meinen Quelltext drüber zu spulten. Vermutlich ist dies mir misslungen, aber wir werden es ja sehen. Wie immer habe ich ein paar frage, die ich am Ende des Beitrages stellen werde.
Ist das so, wie du es dir vorgestellt hast, jerch? Zunächst einmal habe ich alle »QCoreApplication()«-Klassen in »QApplication()« umgeschrieben (Zeile 31 und 57). Ich hoffe einfach, dass ich an dieser Stelle richtig gehandelt habe. Meine Intention: Da ich in meinem Beispiel die »QApplication()«-Klasse verwende (Zeile 173), so verwende ich diese weiterhin.
Allerdings habe ich so meine Zweifel, dass ich dich irgendwie richtig verstanden habe. Denn die komplette Arbeit wurde in diesem Fall wieder in die »run()«-Methode der »TaskWithSignalsAndSlots()«-Klasse verschoben (Zeile 75-97). Ich weiß einfach nicht wohin mit dieser Arbeitsaufgabe. Irgendwo muss es ja ausgeführt werden.
Nun folgen ein paar Fragen: Du hast eine »TaskThread()«-Klasse erstellt, die von »QThread()«-Klasse erbt (Zeile 13). Du hast diese Klasse überschrieben, damit die »run()«-Methode einzelnes Task-Objekte zu initialisieren und auszuführen (Zeilen 25 - 31). Ich frage mal ganz naiv: war das notwendig? Heißt das jetzt, dass die »QThread()«-Klasse ansonsten mehrere Objekte initialisiert und ausgeführt hätte? Welchen Vorteil habe ich jetzt daraus gewonnen?
Als nächstest hast du die »Task()«-Klassen definiert, die von der »QObject()«-Klassen erbt (Zeile 41). Neben den Tatsachen, dass du insgesamt drei Methoden (»processEvents()« (Zeile 56), »start()« (Zeile 59) und »_finished()«(Zeile 62)) definiert hast, wird hier objektweit die »TaskThread()«-Klasse erzeugt (Zeile 53). Anschließend wird dann die »finished()-Methode der »TaskThread()«-Klasse mit der »_finished()«-Methode der »Taks()«-Klasse verbunden (Zeile 54). Die eigentliche Arbeit findet »TaskWithSignalsAndSlots()«-Klasse, die von der »Task()«-Klasse erbt, statt (Zeile 65). Soweit ich das nachvollziehen kann, findet hier die direkte Interaktion zwischen dem Programm und dem Benutzer über die GUI statt.
Jetzt konnte ich zwar einzelne Elemente aufzählen, aber irgendwie habe ich die ganze Dimension noch nicht verstanden. Ich komme mir vor, wie jemand, der in einem Wort alle Buchstaben benennen kann, aber nicht darauf kommt, was das nun für ein Wort ist, und welche Bedeutung dahintersteckt.
Des Weiteren habe ich deine Anmerkung zu der »moveToThread()«-Methode der »WorkObject()«- Klasse nicht ganz verstanden. Was genau und konkret war das falsch? Liegt es daran, weil ich in meinem vorherigen Beispiel die »WorkObject()«- Klasse dialogweit erzeugt und diese dann auf lokaler Ebene in der »on_start()«- Methode der »MyCustomDialog()«- Klasse in die »QThread()«- Klasse verschoben habe? Wieso ist dieser Vorgang mehr als ungünstig?
Besten Dank. Ich habe deine Ausführung mal als Blaupause genommen und versucht meinen Quelltext drüber zu spulten. Vermutlich ist dies mir misslungen, aber wir werden es ja sehen. Wie immer habe ich ein paar frage, die ich am Ende des Beitrages stellen werde.
Code: Alles auswählen
import sys
from time import sleep
from PyQt4.QtCore import QThread, pyqtSignal, Qt, QStringList, QObject, QTimer
from PyQt4.QtGui import QVBoxLayout, QPushButton, QDialog, QProgressBar, QApplication, \
QMessageBox, QTreeWidget, QTreeWidgetItem, QLabel
def create_items(total):
for elem in range(total):
yield elem
class TaskThread(QThread):
'''
Thread container for a Task.
Overrides `run` to initialize and run a single task object.
'''
def __init__(self, obj, args, kwargs, parent=None):
QThread.__init__(self, parent)
obj.moveToThread(self)
self.obj = obj
self.args = args
self.kwargs = kwargs
def run(self):
try:
self.obj.init(*self.args, **self.kwargs)
except:
pass
self.obj.result = self.obj.run()
QApplication.processEvents()
def _close(self):
self.quit()
self.wait()
self.obj.thread = None
self.obj.deleteLater()
self.deleteLater()
class Task(QObject):
'''
Task - a `run` method based self containing theaded task.
Executes the `init` and `run` method within a subthread.
The provided `processEvents` method should be called once in a while
in `run` to be able to catch events and trigger slots.
Any result of `run` will be emitted via the `finished` signal.
'''
finished = pyqtSignal(object)
def __init__(self, *args, **kwargs):
QObject.__init__(self, None)
self.result = None
self.thread = TaskThread(self, args, kwargs, None)
self.thread.finished.connect(self._finished)
def processEvents(self):
QApplication.processEvents()
def start(self):
self.thread.start()
def _finished(self):
self.finished.emit(self.result)
class TaskWithSignalsAndSlots(Task):
'''
Task example with signal and slot dispatching.
'''
notify_counter = pyqtSignal(object)
notify_item = pyqtSignal(object)
def init(self):
self.keep_running = True
def run(self):
total = 1000
x = (total/100*1)
a = x
counter = 0
for item in create_items(total):
counter += 1
self.notify_counter.emit(counter)
self.notify_item.emit(item)
if counter == x:
x += a
sleep(0.5)
if not self.keep_running:
break
self.processEvents()
def stop(self):
self.keep_running = False
class MyCustomDialog(QDialog):
finish = pyqtSignal()
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self.tree = QTreeWidget(self)
self.label = QLabel(self)
self.pushButton_start = QPushButton("Start", self)
self.pushButton_stopp = QPushButton("Stopp", self)
self.pushButton_close = QPushButton("Close", self)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.tree)
layout.addWidget(self.pushButton_start)
layout.addWidget(self.pushButton_stopp)
layout.addWidget(self.pushButton_close)
self.init_PushButton()
def init_PushButton(self):
self.pushButton_start.clicked.connect(self.on_start)
self.pushButton_stopp.clicked.connect(self.on_finish)
self.pushButton_close.clicked.connect(self.on_close)
def fill_tree_widget(self, i):
parent = QTreeWidgetItem(self.tree)
self.tree.addTopLevelItem(parent)
parent.setText(0, unicode(i))
parent.setCheckState(0, Qt.Unchecked)
parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
def on_label(self, i):
self.label.setText("Result: {}".format(int(i)))
def clear_widget(self):
list_widgets = [self.label,
self.tree]
for widget in list_widgets:
widget.clear()
def on_start(self):
self.clear_widget()
task = TaskWithSignalsAndSlots()
task.notify_counter.connect(self.on_label)
task.notify_item.connect(self.fill_tree_widget)
self.finish.connect(task.stop)
timer = QTimer()
timer.setSingleShot(False)
timer.timeout.connect(task.stop)
timer.start()
task.start()
def on_finish(self):
self.finish.emit()
def on_close(self):
self.close()
def closeEvent(self, event):
self.on_finish()
def main():
app = QApplication(sys.argv)
window = MyCustomDialog()
window.resize(600, 400)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Allerdings habe ich so meine Zweifel, dass ich dich irgendwie richtig verstanden habe. Denn die komplette Arbeit wurde in diesem Fall wieder in die »run()«-Methode der »TaskWithSignalsAndSlots()«-Klasse verschoben (Zeile 75-97). Ich weiß einfach nicht wohin mit dieser Arbeitsaufgabe. Irgendwo muss es ja ausgeführt werden.
Nun folgen ein paar Fragen: Du hast eine »TaskThread()«-Klasse erstellt, die von »QThread()«-Klasse erbt (Zeile 13). Du hast diese Klasse überschrieben, damit die »run()«-Methode einzelnes Task-Objekte zu initialisieren und auszuführen (Zeilen 25 - 31). Ich frage mal ganz naiv: war das notwendig? Heißt das jetzt, dass die »QThread()«-Klasse ansonsten mehrere Objekte initialisiert und ausgeführt hätte? Welchen Vorteil habe ich jetzt daraus gewonnen?
Als nächstest hast du die »Task()«-Klassen definiert, die von der »QObject()«-Klassen erbt (Zeile 41). Neben den Tatsachen, dass du insgesamt drei Methoden (»processEvents()« (Zeile 56), »start()« (Zeile 59) und »_finished()«(Zeile 62)) definiert hast, wird hier objektweit die »TaskThread()«-Klasse erzeugt (Zeile 53). Anschließend wird dann die »finished()-Methode der »TaskThread()«-Klasse mit der »_finished()«-Methode der »Taks()«-Klasse verbunden (Zeile 54). Die eigentliche Arbeit findet »TaskWithSignalsAndSlots()«-Klasse, die von der »Task()«-Klasse erbt, statt (Zeile 65). Soweit ich das nachvollziehen kann, findet hier die direkte Interaktion zwischen dem Programm und dem Benutzer über die GUI statt.
Jetzt konnte ich zwar einzelne Elemente aufzählen, aber irgendwie habe ich die ganze Dimension noch nicht verstanden. Ich komme mir vor, wie jemand, der in einem Wort alle Buchstaben benennen kann, aber nicht darauf kommt, was das nun für ein Wort ist, und welche Bedeutung dahintersteckt.
Des Weiteren habe ich deine Anmerkung zu der »moveToThread()«-Methode der »WorkObject()«- Klasse nicht ganz verstanden. Was genau und konkret war das falsch? Liegt es daran, weil ich in meinem vorherigen Beispiel die »WorkObject()«- Klasse dialogweit erzeugt und diese dann auf lokaler Ebene in der »on_start()«- Methode der »MyCustomDialog()«- Klasse in die »QThread()«- Klasse verschoben habe? Wieso ist dieser Vorgang mehr als ungünstig?
Naja, ich hatte den Code jetzt nicht als Vorlage zur Lösung Deines Problems gedacht, aber das geht natürlich damit. Wie Du richtig bemerkt hast, arbeitet die Task-Klasse wiederum mit der `run`-Methode und zusätzlich mit dem händischen Aufruf des Eventdisptachers (dieses `processEvents`), was die Umformung des Problems in eine ereignisgesteuerte Variante erspart. Ich denke allerdings, dass Du wenigstens die Umformung mal gemacht haben solltest, um selbst abschätzen zu können, was wann besser geeignet ist.
Zu allem anderen antworte ich mal im Text:
Generell kannst Du beliebig viele Objekte in einen Thread verschieben und dort nutzen, jepp.
Zu allem anderen antworte ich mal im Text:
QApplication leitet von QCoreApplication ab, daher ist die Umstellung nicht nötig und eigentlich auch nicht sinnvoll, da es für die TaskThread und Task-Klasse die Abhängigkeit zu den Qt GUI-Klassen einführt.Sophus hat geschrieben:... Ist das so, wie du es dir vorgestellt hast, jerch? Zunächst einmal habe ich alle »QCoreApplication()«-Klassen in »QApplication()« umgeschrieben (Zeile 31 und 57). Ich hoffe einfach, dass ich an dieser Stelle richtig gehandelt habe. Meine Intention: Da ich in meinem Beispiel die »QApplication()«-Klasse verwende (Zeile 173), so verwende ich diese weiterhin.
Genau. Versuch doch mal diese simple Zählschleife in eine ereignisgesteuerte Version mittels QTimer zu überführen, dann wird Dir das klarer:Sophus hat geschrieben: Allerdings habe ich so meine Zweifel, dass ich dich irgendwie richtig verstanden habe. Denn die komplette Arbeit wurde in diesem Fall wieder in die »run()«-Methode der »TaskWithSignalsAndSlots()«-Klasse verschoben (Zeile 75-97). Ich weiß einfach nicht wohin mit dieser Arbeitsaufgabe. Irgendwo muss es ja ausgeführt werden.
Code: Alles auswählen
def endless_counter():
c = 0
while True:
# do something
c += 1
Die Ableitung war nötig, um die Task-Funktionalität nach aussen hin zu kapseln, sodass man nur noch ein Task-Exemplar erstellen muss, ggf. Slots an Signale hängt und mit `.start` die Sache ohne weiteres Zutun läuft. Auch hab ich das Ding nur schnell zusammengeschrieben. So finde ich die zusätzliche `init`-Funktion unglücklich - sie versucht, ein Stück weit der Threadaffinität von QObjects entgegen zu kommen, ist aber nach wie vor simpel aushebelbar. Auch fehlt bei der Exception der Fehlertyp. Das sind Sachen, die einem auffallen, wenn man ein zweiten Mal drüberschaut.Sophus hat geschrieben: Nun folgen ein paar Fragen: Du hast eine »TaskThread()«-Klasse erstellt, die von »QThread()«-Klasse erbt (Zeile 13). Du hast diese Klasse überschrieben, damit die »run()«-Methode einzelnes Task-Objekte zu initialisieren und auszuführen (Zeilen 25 - 31). Ich frage mal ganz naiv: war das notwendig? Heißt das jetzt, dass die »QThread()«-Klasse ansonsten mehrere Objekte initialisiert und ausgeführt hätte? Welchen Vorteil habe ich jetzt daraus gewonnen?
Generell kannst Du beliebig viele Objekte in einen Thread verschieben und dort nutzen, jepp.
Das mit der GUI verstehe ich nicht, ich nutze im Bsp. nix GUI-haftes. Für eine schöne API-Spec fehlt der Task-Klasse eine virtuelle `run`-Methode als Platzhalter.Sophus hat geschrieben: Als nächstest hast du die »Task()«-Klassen definiert, die von der »QObject()«-Klassen erbt (Zeile 41). Neben den Tatsachen, dass du insgesamt drei Methoden (»processEvents()« (Zeile 56), »start()« (Zeile 59) und »_finished()«(Zeile 62)) definiert hast, wird hier objektweit die »TaskThread()«-Klasse erzeugt (Zeile 53). Anschließend wird dann die »finished()-Methode der »TaskThread()«-Klasse mit der »_finished()«-Methode der »Taks()«-Klasse verbunden (Zeile 54). Die eigentliche Arbeit findet »TaskWithSignalsAndSlots()«-Klasse, die von der »Task()«-Klasse erbt, statt (Zeile 65). Soweit ich das nachvollziehen kann, findet hier die direkte Interaktion zwischen dem Programm und dem Benutzer über die GUI statt.
Ungünstig an dem Vorgehen ist zum einen, dass die dialogweite Verfügbarkeit suggeriert, dass dieses Objekt ein Kind des Dialoges ist und entsprechend darauf zugegriffen werden kann. Stimmt aber nicht, denn sobald das Objekt im Thread ist, ist das mit großen Einschränkung verbunden. Das `moveToThread` geht beim 2. Mal baden ausbesagten Gründen, einfach mal testen.Sophus hat geschrieben: Des Weiteren habe ich deine Anmerkung zu der »moveToThread()«-Methode der »WorkObject()«- Klasse nicht ganz verstanden. Was genau und konkret war das falsch? Liegt es daran, weil ich in meinem vorherigen Beispiel die »WorkObject()«- Klasse dialogweit erzeugt und diese dann auf lokaler Ebene in der »on_start()«- Methode der »MyCustomDialog()«- Klasse in die »QThread()«- Klasse verschoben habe? Wieso ist dieser Vorgang mehr als ungünstig?
@jerch: Ich werde im Text an entsprechenden Stellen antworten.
Damit du nicht groß suchen musst, hier das ältere Beispiel: Nimm mal die »Print«-Anweisung in Zeile 119 weg, du wirst sehen. Keine Reaktion. Das war auch der Grund, weshalb ich die die Instanz der »WorkObject«-Klasse dialogweit erzeugt habe. Ich gebe zu, das ist keineswegs eine elegante Lösung. Nur damit du meinen Gang nachvollziehen kannst.
Eines vorweg: Deine Vorlage sollte für mich nicht als Lösung dienen. Ich versuchte und versuche immer noch innerlich bis in die kleinste Ecke zu verstehen, was hier passiert. Es bringt mir nichts, wenn ich etwas eins zu eins übernehme. Ich möchte Python lernen und verstehen. Dazu dienen dann Vorlagen für die Analyse sehr hervorragend. Denn das in Worten geschriebene Text wirkt auf mich zu trocken und zu abstrakt. Für Profis ist das natürlich alles offensichtlich und klar nachvollziehbar. Nun habe ich eine gute Überleitung zu meiner Verwirrtheit gebracht. Du redest von Umformung in eine ereignisgesteuerte Variante. Den Satz kann ich lesen, verstehe jedes Wort. Aber was genau meinst du mit Umformung? Kannst du mir dazu bitte ein pseudo-Programm schreiben. Zum Beispiel in diesem Stil "Eigentlich würde man das so machen, aber dann würde man umformen". So das man eine Art »Vorher und Nachher«-Bild hat. Ich sitze nämlich hier und denke mir "Ähm, ja, und?"jerch hat geschrieben:Naja, ich hatte den Code jetzt nicht als Vorlage zur Lösung Deines Problems gedacht, aber das geht natürlich damit. Wie Du richtig bemerkt hast, arbeitet die Task-Klasse wiederum mit der `run`-Methode und zusätzlich mit dem händischen Aufruf des Eventdisptachers (dieses `processEvents`), was die Umformung des Problems in eine ereignisgesteuerte Variante erspart. Ich denke allerdings, dass Du wenigstens die Umformung mal gemacht haben solltest, um selbst abschätzen zu können, was wann besser geeignet ist.
Das verstehe ich nicht. Wenn die »QApplication()«-Klasse von der »QCoreApplication« ableitet, dann ist es doch Jacke wie Hose, ob ich nun die »QApplicationen()«-Klasse in meinem Beispiel-Programm verwende. Denn in meinem Mini-Projekt arbeite ich (natürlich einmal) mit der »QApplication()«-Klasse. Daher verstehe ich nun nicht, was daran weniger sinnvoll sein soll? Da bin ich leider nicht ganz mitgekommen.jerch hat geschrieben:QApplication leitet von QCoreApplication ab, daher ist die Umstellung nicht nötig und eigentlich auch nicht sinnvoll, da es für die TaskThread und Task-Klasse die Abhängigkeit zu den Qt GUI-Klassen einführt.
Habe ich schon - also getestet. Würde ich die Instanzt der »WorkObject()«-Klasse lokal in der »on_start()«Methode erzeugen, so würde das Programm nicht einmal anlaufen. Es sei denn, ich setze am Ende der »on_start()«-Methode ein eine »print«-Anweisung, dann beginnt die Arbeit. Daher bin ich dazu übergegangen, und habe die Instanz der »WorkObject« dialogweit erzeugt. Eine andere und bessere Lösung fiel mir nicht ein.jerch hat geschrieben:Genau. Versuch doch mal diese simple Zählschleife in eine ereignisgesteuerte Version mittels QTimer zu überführen, dann wird Dir das klarer:Ich möchte nicht mit der While-Schleife arbeiten, weil sie für mich wesentlich langsamer ist als die »xrange()«-Methode. Aber bei deiner Aufgabenstellung komme ich zum gleichen Ergebnis. Hier das Ergebnis:Code: Alles auswählen
def endless_counter(): c = 0 while True: # do something c += 1
In der »create_items()«-Funktion sollen später in meinem Projekt die Datensätze aus der Datenbank gelesen werden. Daher auch diesen Generator. Man will ja mit dem Arbeitsspeicher und mit den Ressourcen des Rechners sparsam umgehen. Damit der Generator überhaupt entsprechend angesprochen werden kann, nehme ich hier die »output_items()«-Funktion. So arbeitet man mit Generatoren, richtig? In der »main()«-Funktion erzeuge ich eine Instanz der »QAppliaction()«-Klasse. Anschließend erzeuge ich eine weitere Instanz der »QTimer()«-Klasse. Damit der »QTimer« bei jedem sogenannten »timeout()« etwas auslösen soll, wird die »output_items()«Funktion zum Inhalt der »timeout()«-Methode. Damit hätte ich eine Verbindung hergestellt. Und damit der »QTimer« auch sofort mit der Arbeit beginnt, wird der »start()«-Methode der »QTimer()«-Klasse einfach die Zalh Null übergeben. Und damit das Programm nicht sofort abbricht, wird auf der Instanz der »QApplication()«-Klasse die »exec_()«-Methode ausgeführt. So würde ich meine Aufgabe lösen. Du siehst. Im Grunde finde ich bei mir keinen anderen Weg. Eigentlich der Gleiche Weg wie in all meinen anderen Beispielen.Code: Alles auswählen
import sys from PyQt4.QtCore import QTimer from PyQt4.QtGui import QApplication def create_items(total): for item in xrange(total): yield item def output_items(): for element in create_items(10): print "element", element def main(): app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) create_timer() timer = QTimer() timer.timeout.connect(output_items) timer.start(0) # run event loop so python doesn't exit app.exec_() if __name__ == "__main__": main()
Mir ist dieser Fehler nicht aufgefallen. Verwundert aber auch die wenigsten Ich hatte mich allerdings gefragt, wieso du die »init()«-Methode angewendet hast? Ich hätte die Variable in der »run()«-Methode auf True gesetzt und wäre damit fertig.jerch hat geschrieben: Die Ableitung war nötig, um die Task-Funktionalität nach aussen hin zu kapseln, sodass man nur noch ein Task-Exemplar erstellen muss, ggf. Slots an Signale hängt und mit `.start` die Sache ohne weiteres Zutun läuft. Auch hab ich das Ding nur schnell zusammengeschrieben. So finde ich die zusätzliche `init`-Funktion unglücklich - sie versucht, ein Stück weit der Threadaffinität von QObjects entgegen zu kommen, ist aber nach wie vor simpel aushebelbar. Auch fehlt bei der Exception der Fehlertyp. Das sind Sachen, die einem auffallen, wenn man ein zweiten Mal drüberschaut.
Generell kannst Du beliebig viele Objekte in einen Thread verschieben und dort nutzen, jepp.
Mein Blick würde jetzt sagen: "Ähm, ja... virtuelle Methode... was zum Geier....?"Sophus hat geschrieben: Das mit der GUI verstehe ich nicht, ich nutze im Bsp. nix GUI-haftes. Für eine schöne API-Spec fehlt der Task-Klasse eine virtuelle `run`-Methode als Platzhalter.
verbunden. Das `moveToThread` geht beim 2. Mal baden ausbesagten Gründen, einfach mal testen.jerch hat geschrieben: Ungünstig an dem Vorgehen ist zum einen, dass die dialogweite Verfügbarkeit suggeriert, dass dieses Objekt ein Kind des Dialoges ist und entsprechend darauf zugegriffen werden kann. Stimmt aber nicht, denn sobald das Objekt im Thread ist, ist das mit großen Einschränkung
Damit du nicht groß suchen musst, hier das ältere Beispiel: Nimm mal die »Print«-Anweisung in Zeile 119 weg, du wirst sehen. Keine Reaktion. Das war auch der Grund, weshalb ich die die Instanz der »WorkObject«-Klasse dialogweit erzeugt habe. Ich gebe zu, das ist keineswegs eine elegante Lösung. Nur damit du meinen Gang nachvollziehen kannst.
Code: Alles auswählen
import sys
from time import sleep
from PyQt4.QtCore import QThread, pyqtSignal, Qt, QStringList, QObject, QTimer
from PyQt4.QtGui import QVBoxLayout, QPushButton, QDialog, QProgressBar, QApplication, \
QMessageBox, QTreeWidget, QTreeWidgetItem, QLabel
def create_items(total):
for elem in range(total):
yield elem
class WorkObject(QObject):
notify_progress = pyqtSignal(object)
fire_label = pyqtSignal(object)
finished = pyqtSignal()
def __init__(self, parent=None):
QObject.__init__(self, parent)
def add_items(self):
total = 190000
x = (total/100*1)
a = x
counter = 0
for element in create_items(10000):
counter += 1
self.notify_progress.emit((element))
self.fire_label.emit((counter))
if counter == x:
x += a
sleep(1)
if not self.keep_running:
self.keep_running = True
break
def run(self):
self.keep_running = True
self.add_items()
def stop(self):
self.keep_running = False
class MyCustomDialog(QDialog):
finish = pyqtSignal()
def __init__(self, parent=None):
QDialog.__init__(self, parent)
layout = QVBoxLayout(self)
self.tree = QTreeWidget(self)
self.label = QLabel(self)
self.pushButton_start = QPushButton("Start", self)
self.pushButton_stopp = QPushButton("Stopp", self)
self.pushButton_close = QPushButton("Close", self)
layout.addWidget(self.label)
layout.addWidget(self.tree)
layout.addWidget(self.pushButton_start)
layout.addWidget(self.pushButton_stopp)
layout.addWidget(self.pushButton_close)
self.pushButton_start.clicked.connect(self.on_start)
self.pushButton_stopp.clicked.connect(self.on_finish)
self.pushButton_close.clicked.connect(self.close)
def fill_tree_widget(self, i):
parent = QTreeWidgetItem(self.tree)
self.tree.addTopLevelItem(parent)
parent.setText(0, unicode(i))
parent.setCheckState(0, Qt.Unchecked)
parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
def on_label(self, i):
self.label.setText("Result: {}".format(i))
def on_start(self):
self.tree.clear()
self.label.clear()
task_thread = QThread(self)
work_object = WorkObject()
work_object.fire_label.connect(self.on_label)
work_object.notify_progress.connect(self.fill_tree_widget)
work_object.finished.connect(task_thread.deleteLater)
self.finish.connect(work_object.stop)
work_object.moveToThread(task_thread)
task_thread.started.connect(work_object.run)
timer = QTimer()
# I set the single shot timer on False,
# because I don't want the timer to fires only once,
# it should fires every interval milliseconds
timer.setSingleShot(False)
timer.setInterval(7000)
timer.timeout.connect(work_object.stop)
timer.start()
task_thread.start()
print
def on_finish(self):
self.finish.emit()
def main():
app = QApplication(sys.argv)
window = MyCustomDialog()
window.resize(600, 400)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Hmm wo anfangen, naja ich versuchs mal ganz basal...
Das ist stark vereinfacht das, was `QEventLoop` macht, wenn Du `exec_()` startest und mit `quit()` wieder anhältst. Du benutzt das ja bereits in Deiner GUI-Hauptanwendung, wahrscheinlich ohne genau zu wissen, was es eigentlich macht. (Das Modell ist grob vereinfacht, weil es offen lässt, wie Ereignisse registriert und geholt werden.)
Zurück zur Endlos-Zählschleife - die soll jetzt in unserem Pseudo-System realisiert werden. Das ist relativ einfach, da wir im Ereignissystem schon eine Endlosschleife haben, die wir nutzen können und nur noch die Arbeit eines Schleifendurchlaufes an ein Ereignis hängen müssen:
Jetzt brauchen wir noch was, was das Ereignis COUNTER programmatisch absetzt. Dafür bieten alle Ereignissysteme etwas Entsprechendes an, z.B. `setTimeout` in Javascript oder `QTimer` in Qt.
Was der Pseudocode nicht beachtet, sind Scopingregeln der jeweiligen Sprache. Mit Python umgesetzt, wäre der Einsprungpunkt des Ereignisses COUNTER ein Funktionsaufruf, heisst man muss sicherstellen, das `counter` innerhalb der Funktion auch sichtbar und modifizerbar ist. In Python kann man das per Closure, Objekt oder Parameter sicherstellen (der Vollständigkeit halber auch per global). In PyQt könnte man es z.B. so machen:
Nehmen wir mal die simple Zählschleife von oben und formen die um. Wichtigster Unterschied bei ereignisgesteuert ist, dass Du den Haupt-Kontrollfluss an ein Ereignissystem abgibst. Üblicherweise wird im Ereignissystem die Verarbeitung über eine Endlosschleife abgebildet, hier mal in Pseudocode:Sophus hat geschrieben:Du redest von Umformung in eine ereignisgesteuerte Variante. Den Satz kann ich lesen, verstehe jedes Wort. Aber was genau meinst du mit Umformung? Kannst du mir dazu bitte ein pseudo-Programm schreiben. Zum Beispiel in diesem Stil "Eigentlich würde man das so machen, aber dann würde man umformen". So das man eine Art »Vorher und Nachher«-Bild hat. Ich sitze nämlich hier und denke mir "Ähm, ja, und?"
Code: Alles auswählen
while True
ereignis = hole_ereignis()
# Sonderregel für Abbruch
if ereignis == ABBRUCH
break
verarbeite(ereignis)
Zurück zur Endlos-Zählschleife - die soll jetzt in unserem Pseudo-System realisiert werden. Das ist relativ einfach, da wir im Ereignissystem schon eine Endlosschleife haben, die wir nutzen können und nur noch die Arbeit eines Schleifendurchlaufes an ein Ereignis hängen müssen:
Code: Alles auswählen
counter = 0 # Startzustand
Ereignis COUNTER:
counter = counter + 1
Was der Pseudocode nicht beachtet, sind Scopingregeln der jeweiligen Sprache. Mit Python umgesetzt, wäre der Einsprungpunkt des Ereignisses COUNTER ein Funktionsaufruf, heisst man muss sicherstellen, das `counter` innerhalb der Funktion auch sichtbar und modifizerbar ist. In Python kann man das per Closure, Objekt oder Parameter sicherstellen (der Vollständigkeit halber auch per global). In PyQt könnte man es z.B. so machen:
Code: Alles auswählen
from PyQt4.QtCore import QCoreApplication, QTimer, QObject
import signal
class Counter(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.value = 0
def increment(self):
self.value += 1
print self.value
if __name__ == '__main__':
app = QCoreApplication([])
# triggert ABBRUCH mit Strg+C
signal.signal(signal.SIGINT, lambda s, f: app.quit())
timer = QTimer()
counter = Counter()
# assoziiert increment() mit TIMEOUT Ereignis
timer.timeout.connect(counter.increment)
timer.start()
# startet Ereignishauptschleife
app.exec_()
@jerch: Besten dank. Du leistest tolle Arbeit, und opferst dir die Zeit und Nerven, um mir das alles zu erklären. Aber ich habe das Gefühl, du gehst keineswegs auf meine Generator-Idee ein. Auch habe ich das Gefühl, dass ich mich ständig im Kreis bewege. Nun, ich habe mir mal die Freiheit genommen, und deinen Quelltext ein wenig erweitert. Hier ist das kleine Programm:
Damit der Generator mir nicht nur Zahlen generiert, habe ich eine Liste mit Namen erzeugt. Die Namen wurden willkürlich gesetzt. Wir wollen simulieren, dass die »my_gen()«-Methode der »Counter()«-Klasse die Daten aus der Datenbank liest. Wir tun einfach mal so. Wie du siehst, komme ich zum selben Ergebnis. Der Generator soll mir Zeile für Zeile die Daten aus der Datenbank herausrücken und diese dann auf meinem TreeWidget ausgegeben.
Ich hatte auch noch gehofft, dass du noch einmal kurz auf die Problematik eingehst, dass meine lokal erstelle »WorkObject()«-Instanz nicht ausgeführt werden kann, ohne dass ich die »Print«-Anweisung benutze.
Code: Alles auswählen
from PyQt4.QtCore import QCoreApplication, QTimer, QObject
import signal
class Counter(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.value = 0
def my_gen(self):
names_list = ['Karl',
'Hubert',
'Dagebort',
'Heinz',
'Joseph',
'Paul']
for name in names_list:
yield name
def increment(self):
for element in self.my_gen():
print "Output:", element
if __name__ == '__main__':
app = QCoreApplication([])
# triggert ABBRUCH mit Strg+C
signal.signal(signal.SIGINT, lambda s, f: app.quit())
timer = QTimer()
counter = Counter()
# assoziiert increment() mit TIMEOUT Ereignis
timer.timeout.connect(counter.increment)
timer.start()
# startet Ereignishauptschleife
app.exec_()
Ich hatte auch noch gehofft, dass du noch einmal kurz auf die Problematik eingehst, dass meine lokal erstelle »WorkObject()«-Instanz nicht ausgeführt werden kann, ohne dass ich die »Print«-Anweisung benutze.
@Sophus:
Bei dem Generator bin ich mir nicht sicher, ob er das tut, was Du wolltest. `increment` geht derzeit immer alle Elemente des Generators durch. Falls Du mit `increment` immer nur das nächste Element des Generators haben willst, müsstest Du hier mit `.next()` arbeiten, da Qt keine Ahnung von Pythongeneratoren hat.
Die Sache mit der Print-Anweisung:
Das `print` schindet Zeit, was dem Thread, den Du drüber erstellst, Zeit gibt, um sich fertig zu initialisieren. Ohne `print` fehlt die Zeit und der Garbage Collector von Python räumt das `work_object` ab. Abhilfe - binde das `work_object` an ein Attribut des Threadobjektes, da gehört es semantisch auch hin. Es wird dann nach Threadende mit dem `deleteLater` abgeräumt.
Bei dem Generator bin ich mir nicht sicher, ob er das tut, was Du wolltest. `increment` geht derzeit immer alle Elemente des Generators durch. Falls Du mit `increment` immer nur das nächste Element des Generators haben willst, müsstest Du hier mit `.next()` arbeiten, da Qt keine Ahnung von Pythongeneratoren hat.
Die Sache mit der Print-Anweisung:
Das `print` schindet Zeit, was dem Thread, den Du drüber erstellst, Zeit gibt, um sich fertig zu initialisieren. Ohne `print` fehlt die Zeit und der Garbage Collector von Python räumt das `work_object` ab. Abhilfe - binde das `work_object` an ein Attribut des Threadobjektes, da gehört es semantisch auch hin. Es wird dann nach Threadende mit dem `deleteLater` abgeräumt.
Ok, die »next()«-Funktion ist ein Stichwort. Denn ich möchte in der Tat jedesmal nur das nächste Element des Generators. Dies habe ich gleich versucht umzusetzen. Aber das Ergebnis ist leider nicht wie gewünscht - dazu gleich mehr. Wir sehen, dass ich in Zeile 9 einen Generator erstellt habe. In diesem Generator ist eine Liste. In Zeile 23 verwende ich in der »increment()«-Methode die »next()«-Funktion. Da die »my_gen()«-Methode einen Iterator zurückliefert, so wurde die »my_gen()«-Methode zum Inhalt der »next()«-Funktion. Und durch diese eben genannte Funktion wünsche ich mir, dass sie mir mit jedem Aufruf ein weiteres Element liefert. Was aber passiert, ist, dass immer nur das erste Element aus der Liste zurückgeliefert wird (hier wäre das Element 'Karl').jerch hat geschrieben:@Sophus:
Bei dem Generator bin ich mir nicht sicher, ob er das tut, was Du wolltest. `increment` geht derzeit immer alle Elemente des Generators durch. Falls Du mit `increment` immer nur das nächste Element des Generators haben willst, müsstest Du hier mit `.next()` arbeiten, da Qt keine Ahnung von Pythongeneratoren hat.
Code: Alles auswählen
from PyQt4.QtCore import QCoreApplication, QTimer, QObject
import signal
class Counter(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.value = 0
def my_gen(self):
names_list = ['Karl',
'Hubert',
'Dagebort',
'Heinz',
'Joseph',
'Paul']
#iter_list = iter(names_list)
for name in names_list:
yield name
def increment(self):
#element =
print "Output:", next(self.my_gen())
if __name__ == '__main__':
app = QCoreApplication([])
# triggert ABBRUCH mit Strg+C
signal.signal(signal.SIGINT, lambda s, f: app.quit())
timer = QTimer()
counter = Counter()
# assoziiert increment() mit TIMEOUT Ereignis
timer.timeout.connect(counter.increment)
timer.start()
# startet Ereignishauptschleife
app.exec_()
Das die »Print«-Anweisung etwas mit der Zeit zutun haben könnte, war auch mein Verdacht. Allerdings traue ich mir selbst wenig zu, daher habe ich mich auch an dich gewendet. Hätte ja sein können, dass intern eine etwas andere Kette ausgelöst wird. Nun zum Eigentlich: Du sagtest, ich solle die Instanz der »WorkObject()«-Klasse an ein Attribut des »QThread()«-Objektes, richtig? Ich habe hier nur mal die »on_start()«-Methode der »QDialog()«-Klasse herausgenommen. Wird hier nicht bereits in Zeile 16 eine Verbindung hergestellt? Die »work_object«-Instanz wird an die »run()«-Methode der »task_thread«-Instanz gebunden. Ich ging immer davon aus, dass dies ausreiche. Aber du schriebst, ich solle an ein Attribut der »task_thread«-Instanz binden, korrekt? An welches Attribut? Mir leuchtet leider nicht ein, an welches Attribut ich binden soll.jerch hat geschrieben: Die Sache mit der Print-Anweisung:
Das `print` schindet Zeit, was dem Thread, den Du drüber erstellst, Zeit gibt, um sich fertig zu initialisieren. Ohne `print` fehlt die Zeit und der Garbage Collector von Python räumt das `work_object` ab. Abhilfe - binde das `work_object` an ein Attribut des Threadobjektes, da gehört es semantisch auch hin. Es wird dann nach Threadende mit dem `deleteLater` abgeräumt.
Außerdem habe ich diese unten aufgeführte Methode ein wenig aufgeräumt. Ich habe die »work_object«-Instanz, sobald sie fertig ist (»finished«) mit der »quit()«-Methode der »task_thread«-Instanz verbunden. Dadurch soll dann der Thread auch beendet werden. Damit gleich nach dem Ende der »task_thread«-Instanz aufgeräumt werden soll, bin ich dazu übergegangen, und habe in Zeile 18 das Ende dieser Instanz (»finished«) mit der »deleteLater()«-Methode der gleichen Instanz verbunden. Sprich, wenn die »task_thread«-Instanz beendet wurde, soll sie dann im Anschluss mit der »deleteLater()«-Methode verbunden werden, damit die Aufräumarbeit beginnen kann.
Code: Alles auswählen
def on_start(self):
self.tree.clear()
self.label.clear()
task_thread = QThread(self)
work_object = WorkObject()
work_object.fire_label.connect(self.on_label)
work_object.notify_progress.connect(self.fill_tree_widget)
work_object.finished.connect(task_thread.quit)
self.finish.connect(work_object.stop)
work_object.moveToThread(task_thread)
task_thread.started.connect(work_object.run)
task_thread.finished.connect(task_thread.deleteLater)
timer = QTimer()
# I set the single shot timer on False,
# because I don't want the timer to fires only once,
# it should fires every interval milliseconds
timer.setSingleShot(False)
#timer.setInterval(7000)
timer.timeout.connect(work_object.stop)
timer.start(0)
task_thread.start()
print
@Sophus:
Einen Generator erhältst Du, in dem Du eine Generatorfunktion aufrufst. Mit `next()` bekommst Du das nächste Element, bei einem neuen Generator ist das logischerweise das erste. Genau das hast Du im Code geschrieben. Du musst den Generator vor Benutzung von `increment` erstellen, und beim `next` selbst noch die StopIteration-Exception abfangen. Mit Letzterem würde ich an ein Signal absetzen, was den Timer stoppt, sonst läuft der feuchtfröhlich weiter in die Exception rein.
Mit Binden an ein Atrribut meine ich, einfach an einen Namen hängen, z.B.:Auch sollte bei der Threadlösung der Timer in den Subthread, sonst läuft dieser selbst im Hauptthread und dessen Timout-Signal muss jedesmal durch das aufwändigere Eventdispatching von Hauptthread zu Subthread durch. Und wenn Du dann noch die Threadlösung mit der Generatorsache kombinierst, bist Du bei der ereignisgesteuerten Variante angekommen.
Einen Generator erhältst Du, in dem Du eine Generatorfunktion aufrufst. Mit `next()` bekommst Du das nächste Element, bei einem neuen Generator ist das logischerweise das erste. Genau das hast Du im Code geschrieben. Du musst den Generator vor Benutzung von `increment` erstellen, und beim `next` selbst noch die StopIteration-Exception abfangen. Mit Letzterem würde ich an ein Signal absetzen, was den Timer stoppt, sonst läuft der feuchtfröhlich weiter in die Exception rein.
Mit Binden an ein Atrribut meine ich, einfach an einen Namen hängen, z.B.:
Code: Alles auswählen
...
task_thread = QThread(self)
task_thread.work = WorkObject()
@jerch: Vielen dank. Ich lerne echt sehr viel
Ich habe mal versucht all deine Stichwörter und Anregungen zu berücksichtigen. Die »self.timer«-Instanz habe ich in Zeile 15 erzeugt. Also im Subthread (»Counter()«). Wie du mir gesagt hast, habe ich in Zeile 13 den »self.element«-Generator erzeugt, ehe der Generator benutzt wird. Ich gebe zu, die Namensgebungen sind nicht getroffen. Aber ich denke, dies können wir mal für einen kleinen Augenblick vernachlässigen In Zele 4 habe ich eine lose Funktion erstellt, wohin alle Nachrichten kommen. Des Weiteren wird das Programm beendet, sobald der Durchlauf wertig ist. Das heißt, dadurch habe ich einen Grund mit der »pyqtSignal«-Methode zu arbeiten.
Eines jedoch konnte ich nicht berücksichtigen. Ich habe den Timer direkt in der StopIteration-Exception gestoppt - ohne ein Signal darauf abzusetzen. Schließlich ist der Timer in unmittelbarer Nähe.
Aber vermutlich liege ich gänzlich falsch, und du schüttelst mit dem Kopf. Ich bitte dann um etwas Nachsicht
Ich habe mal versucht all deine Stichwörter und Anregungen zu berücksichtigen. Die »self.timer«-Instanz habe ich in Zeile 15 erzeugt. Also im Subthread (»Counter()«). Wie du mir gesagt hast, habe ich in Zeile 13 den »self.element«-Generator erzeugt, ehe der Generator benutzt wird. Ich gebe zu, die Namensgebungen sind nicht getroffen. Aber ich denke, dies können wir mal für einen kleinen Augenblick vernachlässigen In Zele 4 habe ich eine lose Funktion erstellt, wohin alle Nachrichten kommen. Des Weiteren wird das Programm beendet, sobald der Durchlauf wertig ist. Das heißt, dadurch habe ich einen Grund mit der »pyqtSignal«-Methode zu arbeiten.
Eines jedoch konnte ich nicht berücksichtigen. Ich habe den Timer direkt in der StopIteration-Exception gestoppt - ohne ein Signal darauf abzusetzen. Schließlich ist der Timer in unmittelbarer Nähe.
Aber vermutlich liege ich gänzlich falsch, und du schüttelst mit dem Kopf. Ich bitte dann um etwas Nachsicht
Code: Alles auswählen
from PyQt4.QtCore import QCoreApplication, QTimer, QObject, pyqtSignal
def output(text):
print "Output:", text
class Counter(QObject):
notify_progress = pyqtSignal(str)
finish_progress = pyqtSignal()
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.element = self.my_gen()
self.timer = QTimer()
# assoziiert increment() mit TIMEOUT Ereignis
self.timer.timeout.connect(self.increment)
self.timer.start()
def my_gen(self):
names_list = ['Karl',
'Hubert',
'Dagebort',
'Heinz',
'Joseph',
'Paul']
for name in names_list:
yield name
def increment(self):
try:
self.notify_progress.emit(next(self.element))
except StopIteration:
self.notify_progress.emit('Break the loop')
self.finish_progress.emit()
self.timer.stop()
if __name__ == '__main__':
app = QCoreApplication([])
counter = Counter()
counter.notify_progress.connect(output)
counter.finish_progress.connect(app.quit)
# startet Ereignishauptschleife
app.exec_()
@Sophus: Ja super, das passt so. Zwei Dinge kannst Du an dem Bsp. erkennen - einmal Kapselung. Counter hat nach aussen nur zwei Signale. Ansonsten ist die Klasse self-contained, also enthält ihre Logik selbst und kümmert sich auch selbst um die Daten (ist ja nicht viel aber es geht eher ums Prinzip). Ich weise Dich explizit auf den Sachverhalt hin, weil ich unsere Diskussionen um sinnvolle Programmaufteilung und "wer ist wessen Kind" von vor Monaten noch in Erinnerung habe. Counter wäre hier ein Beispiel für gute Kapselung.
Die zweite Sache - jetzt hast Du "Multithreading für Arme", also ohne Threads Nebenläufigkeit von Aufgaben geschaffen. Oftmals braucht man nämlich gar keinen zusätzlichen Thread, weil die Arbeit sich gut zerlegen lässt und im Hauptthread machbar ist. Javascript lässt sich übrigens nur so programmieren.
Die zweite Sache - jetzt hast Du "Multithreading für Arme", also ohne Threads Nebenläufigkeit von Aufgaben geschaffen. Oftmals braucht man nämlich gar keinen zusätzlichen Thread, weil die Arbeit sich gut zerlegen lässt und im Hauptthread machbar ist. Javascript lässt sich übrigens nur so programmieren.
@jerch: Cool, ich habe also doch was gelernt Bezüglich der Kapselung: Ich denke, sobald man die eigentliche Logik in ein anderes Modul auslagert, und dieses Modul bei bedarf dann in eine sogenannte View-Klasse lädt, kapselt man auch, korrekt? Denn hier wird Logik und View getrennt, und somit ist eine Kapselung auch gegeben. So denke ich zumidnest und so handhabe ich das auch. In meinen View-Klassen kommen nur PyQt-relevanten Sachen rein. Aber da du es expliziet noch einmal erwähnst: Heißt das im Umkehrschluss, dass meine Beispiele zuvor keine Kapselungen hatten? Ich meine, auch wenn ich die Arbeitsaufgaben falsch angepackt habe, fanden die Aufgaben immer in einem Subthread statt. Anfangs nur in einem QThread(), später dann im QObject(). Die einpliziete Erwähnung hat mich etwas irritiert.
Aber eines möchte ich noch nachfragen: Du sprachst (insbesondere in diesem Beitrag) öfters von ereignisorientiert. Ist mein Beispiel deshalb ereignisorientiert, weil die Aufgabe über den QTimer angetrieben wird? Im Grunde hätte das ach ein timer-Modul das gleiche getan, und dies wäre auch ereignisorientiert, oder? Ich habe zwar alles verstanden, was ich dazu unter deiner Federführung zusammengeschrieben habe, aber mir fehlt irgendwie noch der "Aha"-Effekt.
Später werde ich noch einmal mein altes Beispiel mit meinen neuen Errungenschaften vervollständigen. Wenn ich dann deinen "Segen" bekomme, habe ich für mich schon einmal eine richtige Grundlage.
Aber eines möchte ich noch nachfragen: Du sprachst (insbesondere in diesem Beitrag) öfters von ereignisorientiert. Ist mein Beispiel deshalb ereignisorientiert, weil die Aufgabe über den QTimer angetrieben wird? Im Grunde hätte das ach ein timer-Modul das gleiche getan, und dies wäre auch ereignisorientiert, oder? Ich habe zwar alles verstanden, was ich dazu unter deiner Federführung zusammengeschrieben habe, aber mir fehlt irgendwie noch der "Aha"-Effekt.
Später werde ich noch einmal mein altes Beispiel mit meinen neuen Errungenschaften vervollständigen. Wenn ich dann deinen "Segen" bekomme, habe ich für mich schon einmal eine richtige Grundlage.
Ob Du gut gekapselt hast, merkst Du daran, ob etwas wiederverwendbar ist, ohne den Code zerschnippeln zu müssen. Das kann eine reine Daten-, Logik- oder Viewkomponente sein, oder auch eine Melange von all dem (der DateiÖffnen-Dialog von Qt ist hierfür ein Bsp). Ob eine strikte Trennung von View und Logik zweckmäßig ist, hängt immer von der Problemstellung ab.Sophus hat geschrieben:@jerch: Cool, ich habe also doch was gelernt Bezüglich der Kapselung: Ich denke, sobald man die eigentliche Logik in ein anderes Modul auslagert, und dieses Modul bei bedarf dann in eine sogenannte View-Klasse lädt, kapselt man auch, korrekt? Denn hier wird Logik und View getrennt, und somit ist eine Kapselung auch gegeben. So denke ich zumidnest und so handhabe ich das auch. In meinen View-Klassen kommen nur PyQt-relevanten Sachen rein. Aber da du es expliziet noch einmal erwähnst: Heißt das im Umkehrschluss, dass meine Beispiele zuvor keine Kapselungen hatten? Ich meine, auch wenn ich die Arbeitsaufgaben falsch angepackt habe, fanden die Aufgaben immer in einem Subthread statt. Anfangs nur in einem QThread(), später dann im QObject(). Die einpliziete Erwähnung hat mich etwas irritiert.
Genau, Du hast die Arbeit der Schleife zerlegt und die Teilschritte werden über ein QTimer-Ereignis über die Ereignisschleife "angetrieben". Der eigentliche Vorteil ist am Bsp. noch nicht ersichtlich, fällt aber im Zusammenspiel mit weiteren Ereignissen sofort ins Auge. So kannst Du den Counter 5 oder 10mal parallel starten und diese werden scheinbar gleichzeitig abgearbeitet (deshalb "Threading für Arme"). Da Qt Benutzereingaben und die GUI selbst per Ereignisschleife verarbeitet/updatet, friert die GUI nicht mehr spürbar ein während Counter die Schleife durchläuft. Aber Obacht: Wenn ein Teilschritt zulange brauchen sollte, wird das wieder spürbar und die GUI "hakelt". Chrome gibt z.B. 60 FPS für eine flüssige Darstellung an, was bedeutet, dass eine Javascript-Teilaufgabe in 10-12 ms fertig sein sollte, damit es nicht ruckelt.Sophus hat geschrieben: Aber eines möchte ich noch nachfragen: Du sprachst (insbesondere in diesem Beitrag) öfters von ereignisorientiert. Ist mein Beispiel deshalb ereignisorientiert, weil die Aufgabe über den QTimer angetrieben wird? Im Grunde hätte das ach ein timer-Modul das gleiche getan, und dies wäre auch ereignisorientiert, oder? Ich habe zwar alles verstanden, was ich dazu unter deiner Federführung zusammengeschrieben habe, aber mir fehlt irgendwie noch der "Aha"-Effekt.
@jerch: Jetzt bin ich dazu gekommen, mein vorheriges Beispiel mit den neuen Errungenschaften zu kombinieren. Jetzt präsentiere ich dir quasi das "Endergebnis" dieses Beispiels und hätte gern deinen "Segen", damit ich für mich sagen kann "Supi, damit kann ich schon mal arbeiten" und das ich dann eine gewisse Sicherheit im Gefühl bekomme. Den Quelltext siehst du weiter unten.
Wie du sehen wirst, habe ich die »QCoreApplication()«-Klasse gegen »QApplication« ausgetauscht. Damit das Beispiel nicht nur mit fünf Elementen aus einer Liste arbeitet, habe ich wieder die »xrange()« herangeholt. Das Beispiel soll ja auch seine Arbeit demonstrieren. Auch habe ich die »moveToThread()«-Methode herangezogen, damit die Arbeit auch in ein Thread verschoben wird. Oder sollte ich das nicht machen?
Was mir allerdings auffällt. Die GUI ruckelt zunächst einmal nicht. Das ist schon mal super. Aber der QTimer "stößt" die Arbeit ziemlich langsam an, oder etwa nicht? Ehe die 10.000 Items nun durchlaufen wurden, vergehen fast 2 Minuten. Ich habe mit der Stoppuhr meines Smartphones gemessen. Ist ein Bisschen zu "langsam" oder?
Wie du sehen wirst, habe ich die »QCoreApplication()«-Klasse gegen »QApplication« ausgetauscht. Damit das Beispiel nicht nur mit fünf Elementen aus einer Liste arbeitet, habe ich wieder die »xrange()« herangeholt. Das Beispiel soll ja auch seine Arbeit demonstrieren. Auch habe ich die »moveToThread()«-Methode herangezogen, damit die Arbeit auch in ein Thread verschoben wird. Oder sollte ich das nicht machen?
Was mir allerdings auffällt. Die GUI ruckelt zunächst einmal nicht. Das ist schon mal super. Aber der QTimer "stößt" die Arbeit ziemlich langsam an, oder etwa nicht? Ehe die 10.000 Items nun durchlaufen wurden, vergehen fast 2 Minuten. Ich habe mit der Stoppuhr meines Smartphones gemessen. Ist ein Bisschen zu "langsam" oder?
Code: Alles auswählen
import sys
from PyQt4.QtCore import QTimer, QObject, pyqtSignal, \
QThread, Qt
from PyQt4.QtGui import QDialog, QLabel, QPushButton, \
QApplication, QVBoxLayout, QTreeWidget, QTreeWidgetItem
def output(text):
print "Output:", text
class Counter(QObject):
notify_progress = pyqtSignal(str)
notify_item = pyqtSignal(object)
finish_progress = pyqtSignal()
fire_label = pyqtSignal(int)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.element = self.my_gen()
self.timer = QTimer()
# assoziiert increment() mit TIMEOUT Ereignis
self.timer.setSingleShot(False)
self.timer.timeout.connect(self.increment)
self.timer.start()
def my_gen(self):
count = 0
for name in xrange(10000):
count += 1
self.fire_label.emit(count)
yield name
def increment(self):
try:
self.notify_item.emit(next(self.element))
except StopIteration:
self.notify_progress.emit('Break the loop')
self.finish_progress.emit()
self.timer.stop()
def stop(self):
self.notify_progress.emit('Stop the loop')
self.timer.stop()
class MyCustomDialog(QDialog):
finish = pyqtSignal()
def __init__(self, parent=None):
QDialog.__init__(self, parent)
layout = QVBoxLayout(self)
self.tree = QTreeWidget(self)
self.label = QLabel(self)
self.pushButton_start = QPushButton("Start", self)
self.pushButton_stopp = QPushButton("Stopp", self)
self.pushButton_close = QPushButton("Close", self)
layout.addWidget(self.label)
layout.addWidget(self.tree)
layout.addWidget(self.pushButton_start)
layout.addWidget(self.pushButton_stopp)
layout.addWidget(self.pushButton_close)
self.pushButton_start.clicked.connect(self.on_start)
self.pushButton_stopp.clicked.connect(self.on_finish)
self.pushButton_close.clicked.connect(self.close)
def fill_tree_widget(self, i):
parent = QTreeWidgetItem(self.tree)
self.tree.addTopLevelItem(parent)
parent.setText(0, unicode(i))
parent.setCheckState(0, Qt.Unchecked)
parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
def on_label(self, i):
self.label.setText("Result: {}".format(i))
def on_start(self):
self.tree.clear()
self.label.clear()
task_thread = QThread(self)
task_thread.work = Counter()
task_thread.work.fire_label.connect(self.on_label)
task_thread.work.notify_progress.connect(output)
task_thread.work.notify_item.connect(self.fill_tree_widget)
task_thread.work.finish_progress.connect(task_thread.quit)
self.finish.connect(task_thread.work.stop)
task_thread.work.moveToThread(task_thread)
#task_thread.started.connect(task_thread.work)
task_thread.finished.connect(task_thread.deleteLater)
task_thread.start()
def on_finish(self):
self.finish.emit()
def main():
app = QApplication(sys.argv)
window = MyCustomDialog()
window.resize(600, 400)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
@Sophus:
Ohne Thread sollte es sogar etwas schneller sein. Die Lösung mit Thread, die Du jetzt gebaut hast, macht vermutlich wieder nicht das, was Du erwartet hattest. Mit Thread musst Du Dir genau über die Threadgrenzen im Klaren sein, sonst kommt da Verwurschteltes raus. Derzeit passiert folgendes:
- Counter.__init__ wird im Hauptthread aufgerufen (vor moveToThread)
- Timer in Counter wird in __init__ erstellt und gestartet --> Timer läuft im Hauptthread und wird daher gegen GUI-Ereignisschleife gesynct
- counter.moveToThread(...)
- `increment` wird im Subthread aufgerufen
Im Endergebnis hast Du in etwa folgende Konstellation für einen Timer-increment-Zyklus:
--> QTimer.TIMEOUT(Hauptthread) --> Subthread-eventloop --> increment+notify_item-Signal (Subthread) --> Hauptthread-eventloop --> output (Hauptthread) -->
Das ist unnötig kompliziert und weil es gegen die Ereignisschleife im Hauptthread synct (QTimer liegt dort) und die Signale zwischen den Threads dispatchen muss, auch so langsam.
Eine Möglichkeit der Vereinfachung wäre, alles in `Counter.__init__` in eine zweite Methode zu packen, welche mit `task_thread.started` aufgerufen wird. Damit ist der Timer im Subthread, was deutlich schneller die Liste abarbeitet. Aber Achtung: Wenn die Liste schneller abgearbeitet wird, heisst das auch, dass auf GUI-Seite mehr Elemente pro Zeit eingehängt und gezeichnet werden müssen, was wiederum zu GUI-Hängern führen kann. Generell lässt sich dazu nur sagen, dass der Subthread wenig Sinn ergibt, wenn dieser deutlich weniger pro Arbeitschritt zu tun hat als der Hauptthread (was hier der Fall ist: Subthread - next in Generator, Hauptthread - Eventverarbeitung + GUI-Elemente anlegen + neuzeichnen). Evtl. lässt sich sowas dann noch mit anderer Granularität beheben (z.B. mehrere Elemente pro 1x Neuzeichnen usw.)
Zweite Möglichkeit - Counter im Hauptthread lassen. Geht, wenn Counter nicht schneller werden muss und den Hauptthread nicht zu sehr belastet.
Ohne Thread sollte es sogar etwas schneller sein. Die Lösung mit Thread, die Du jetzt gebaut hast, macht vermutlich wieder nicht das, was Du erwartet hattest. Mit Thread musst Du Dir genau über die Threadgrenzen im Klaren sein, sonst kommt da Verwurschteltes raus. Derzeit passiert folgendes:
- Counter.__init__ wird im Hauptthread aufgerufen (vor moveToThread)
- Timer in Counter wird in __init__ erstellt und gestartet --> Timer läuft im Hauptthread und wird daher gegen GUI-Ereignisschleife gesynct
- counter.moveToThread(...)
- `increment` wird im Subthread aufgerufen
Im Endergebnis hast Du in etwa folgende Konstellation für einen Timer-increment-Zyklus:
--> QTimer.TIMEOUT(Hauptthread) --> Subthread-eventloop --> increment+notify_item-Signal (Subthread) --> Hauptthread-eventloop --> output (Hauptthread) -->
Das ist unnötig kompliziert und weil es gegen die Ereignisschleife im Hauptthread synct (QTimer liegt dort) und die Signale zwischen den Threads dispatchen muss, auch so langsam.
Eine Möglichkeit der Vereinfachung wäre, alles in `Counter.__init__` in eine zweite Methode zu packen, welche mit `task_thread.started` aufgerufen wird. Damit ist der Timer im Subthread, was deutlich schneller die Liste abarbeitet. Aber Achtung: Wenn die Liste schneller abgearbeitet wird, heisst das auch, dass auf GUI-Seite mehr Elemente pro Zeit eingehängt und gezeichnet werden müssen, was wiederum zu GUI-Hängern führen kann. Generell lässt sich dazu nur sagen, dass der Subthread wenig Sinn ergibt, wenn dieser deutlich weniger pro Arbeitschritt zu tun hat als der Hauptthread (was hier der Fall ist: Subthread - next in Generator, Hauptthread - Eventverarbeitung + GUI-Elemente anlegen + neuzeichnen). Evtl. lässt sich sowas dann noch mit anderer Granularität beheben (z.B. mehrere Elemente pro 1x Neuzeichnen usw.)
Zweite Möglichkeit - Counter im Hauptthread lassen. Geht, wenn Counter nicht schneller werden muss und den Hauptthread nicht zu sehr belastet.
@Sophus:
Hab gerade erst gesehen, dass Du `moveToThread` sehr spät nach den Signal-Slot-Verbindungen aufrufst. Damit wird Counter effektiv im Hauptthread abgearbeitet. Du musst hier beachten, dass `connect` threadaware ist. Wenn Du das auf ein Objekt im selben Thread anwendest, nimmt Qt eine Abkürzung und registriert den Slotaufruf direkt. Für ein Objekt im Fremdthread dispatcht es Signale in dessen QEventloop.
Du hast quasi Direktverbindungen hergestellt und dann das Objekt in einen anderen Thread verschoben. Direkte Aufrufe unterliegen immer dem selben Threadkontext, daher wird `increment` im Hauptthread aufgerufen.
Abhilfe - Signal-Slot-Verbindungen nach `moveToThread` erstellen.
Hab gerade erst gesehen, dass Du `moveToThread` sehr spät nach den Signal-Slot-Verbindungen aufrufst. Damit wird Counter effektiv im Hauptthread abgearbeitet. Du musst hier beachten, dass `connect` threadaware ist. Wenn Du das auf ein Objekt im selben Thread anwendest, nimmt Qt eine Abkürzung und registriert den Slotaufruf direkt. Für ein Objekt im Fremdthread dispatcht es Signale in dessen QEventloop.
Du hast quasi Direktverbindungen hergestellt und dann das Objekt in einen anderen Thread verschoben. Direkte Aufrufe unterliegen immer dem selben Threadkontext, daher wird `increment` im Hauptthread aufgerufen.
Abhilfe - Signal-Slot-Verbindungen nach `moveToThread` erstellen.
@jerch: Das ich durch mein vorheriges Beispiel vieles durcheinander gebracht habe, ist mir gar nicht bewusst gewesen. Als du mich erst darauf hingewiesen und aufgeklärt hast, wurde mir das klar. Daher ein großes Danke an dieser Stelle.
Ich habe all deine Anmerkungen berücksichtigt, und noch eine kleine Sache hinzugetan. In Zeile 30 habe ich einen Interval auf den QTimer gesetzt. Ohne diesen Interval hat die GUI einfach zu sehr gehängt. Damit etwas Luft dazwischen bleibt, setzte ich einfach den Interval. Im Interval-Kontext entspricht 1.000 eine Sekunde, korrekt? Dann ist die Zahl 1 Millisekunde? Jedenfalls läuft die GUI-Seite füssig, und der Timer arbeitet etwas schneller
Ich habe all deine Anmerkungen berücksichtigt, und noch eine kleine Sache hinzugetan. In Zeile 30 habe ich einen Interval auf den QTimer gesetzt. Ohne diesen Interval hat die GUI einfach zu sehr gehängt. Damit etwas Luft dazwischen bleibt, setzte ich einfach den Interval. Im Interval-Kontext entspricht 1.000 eine Sekunde, korrekt? Dann ist die Zahl 1 Millisekunde? Jedenfalls läuft die GUI-Seite füssig, und der Timer arbeitet etwas schneller
Code: Alles auswählen
import sys
from PyQt4.QtCore import QTimer, QObject, pyqtSignal, \
QThread, Qt
from PyQt4.QtGui import QDialog, QLabel, QPushButton, \
QApplication, QVBoxLayout, QTreeWidget, QTreeWidgetItem
def output(text):
print "Output:", text
class Counter(QObject):
notify_progress = pyqtSignal(str)
notify_item = pyqtSignal(object)
finish_progress = pyqtSignal()
fire_label = pyqtSignal(int)
def __init__(self, parent=None):
QObject.__init__(self, parent)
def init_object(self):
self.element = self.my_gen()
self.timer = QTimer()
# assoziiert increment() mit TIMEOUT Ereignis
self.timer.setSingleShot(False)
self.timer.setInterval(1)
self.timer.timeout.connect(self.increment)
self.timer.start()
def my_gen(self):
count = 0
for name in xrange(100000):
count += 1
self.fire_label.emit(count)
yield name
def increment(self):
try:
self.notify_item.emit(next(self.element))
except StopIteration:
self.notify_progress.emit('Break the loop')
self.finish_progress.emit()
self.timer.stop()
def stop(self):
self.notify_progress.emit('Stop the loop')
self.timer.stop()
class MyCustomDialog(QDialog):
finish = pyqtSignal()
def __init__(self, parent=None):
QDialog.__init__(self, parent)
layout = QVBoxLayout(self)
self.tree = QTreeWidget(self)
self.label = QLabel(self)
self.pushButton_start = QPushButton("Start", self)
self.pushButton_stopp = QPushButton("Stopp", self)
self.pushButton_close = QPushButton("Close", self)
layout.addWidget(self.label)
layout.addWidget(self.tree)
layout.addWidget(self.pushButton_start)
layout.addWidget(self.pushButton_stopp)
layout.addWidget(self.pushButton_close)
self.pushButton_start.clicked.connect(self.on_start)
self.pushButton_stopp.clicked.connect(self.on_finish)
self.pushButton_close.clicked.connect(self.close)
def fill_tree_widget(self, i):
parent = QTreeWidgetItem(self.tree)
self.tree.addTopLevelItem(parent)
parent.setText(0, unicode(i))
parent.setCheckState(0, Qt.Unchecked)
parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
def on_label(self, i):
self.label.setText("Result: {}".format(i))
def on_start(self):
self.tree.clear()
self.label.clear()
task_thread = QThread(self)
task_thread.work = Counter()
task_thread.work.moveToThread(task_thread)
task_thread.work.fire_label.connect(self.on_label)
task_thread.work.notify_progress.connect(output)
task_thread.work.notify_item.connect(self.fill_tree_widget)
task_thread.work.finish_progress.connect(task_thread.quit)
self.finish.connect(task_thread.work.stop)
task_thread.started.connect(task_thread.work.init_object)
task_thread.finished.connect(task_thread.deleteLater)
task_thread.start()
def on_finish(self):
self.finish.emit()
def main():
app = QApplication(sys.argv)
window = MyCustomDialog()
window.resize(600, 400)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
@Sophus:
Prima, jetzt haste fast eine Lehrbuchlösung (naja nach Aufräumen und Dubletten raus ). Und falls Du die Lust noch nicht verloren hast, kannste mit dem Wissen ja mal einen Threadpool mit Workerthreads bauen, welchen man Arbeit zuschanzen kann
Prima, jetzt haste fast eine Lehrbuchlösung (naja nach Aufräumen und Dubletten raus ). Und falls Du die Lust noch nicht verloren hast, kannste mit dem Wissen ja mal einen Threadpool mit Workerthreads bauen, welchen man Arbeit zuschanzen kann