Fragen:
- Was ist die beste Methode, um den Fortschritt eines Threads zu verfolgen, ohne die GUI zu sperren ("Reagiert nicht")?
- Im Allgemeinen, was sind die besten Praktiken für Threadbearbeitung im Hinblick auf die GUI-Entwicklung?
Frage Hintergrund:
- Ich habe eine PyQt GUI für Windows.
- Sie wird verwendet, um Sets von HTML-Dokumenten zu verarbeiten.
- Es dauert zwischen drei Sekunden und drei Stunden, um ein Set von Dokumenten zu verarbeiten.
- Ich möchte in der Lage sein, mehrere Sets gleichzeitig zu verarbeiten.
- Ich möchte nicht, dass die GUI blockiert.
- Ich betrachte das Threading-Modul, um dies zu erreichen.
- Ich bin relativ neu im Bereich Threading.
- Die GUI hat eine Fortschrittsanzeige.
- Ich möchte, dass sie den Fortschritt des ausgewählten Threads anzeigt.
- Ergebnisse des ausgewählten Threads anzeigen, wenn er fertig ist.
- Ich benutze Python 2.5.
Mein Gedanke: Die Threads sollen ein QtSignal aussenden, wenn der Fortschritt aktualisiert wird, was eine Funktion auslöst, die die Fortschrittsanzeige aktualisiert. Außerdem soll ein Signal gesendet werden, wenn die Verarbeitung abgeschlossen ist, sodass die Ergebnisse angezeigt werden können.
#HINWEIS: Dies ist Beispielcode für meine Idee, du musst dies nicht lesen, um die Frage(n) zu beantworten.
import threading
from PyQt4 import QtCore, QtGui
import re
import copy
class ProcessingThread(threading.Thread, QtCore.QObject):
__pyqtSignals__ = ( "progressUpdated(str)",
"resultsReady(str)")
def __init__(self, docs):
self.docs = docs
self.progress = 0 #int zwischen 0 und 100
self.results = []
threading.Thread.__init__(self)
def getResults(self):
return copy.deepcopy(self.results)
def run(self):
num_docs = len(self.docs) - 1
for i, doc in enumerate(self.docs):
processed_doc = self.processDoc(doc)
self.results.append(processed_doc)
new_progress = int((float(i)/num_docs)*100)
#Signal nur aussenden, wenn der Fortschritt sich geändert hat
if self.progress != new_progress:
self.emit(QtCore.SIGNAL("progressUpdated(str)"), self.getName())
self.progress = new_progress
if self.progress == 100:
self.emit(QtCore.SIGNAL("resultsReady(str)"), self.getName())
def processDoc(self, doc):
''' Dies ist trivial verkürzt '''
return re.findall(']*>.*?', doc)
class GuiApp(QtGui.QMainWindow):
def __init__(self):
self.processing_threads = {} #{'thread_name': Thread(processing_thread)}
self.progress_object = {} #{'thread_name': int(thread_progress)}
self.results_object = {} #{'thread_name': []}
self.selected_thread = '' #'thread_name'
def processDocs(self, docs):
#neuen Thread erstellen
p_thread = ProcessingThread(docs)
thread_name = "example_thread_name"
p_thread.setName(thread_name)
p_thread.start()
#Thread zur Thread-Liste hinzufügen
self.processing_threads[thread_name] = p_thread
#Fortschrittsobjekt initialisieren für diesen Thread
self.progress_object[thread_name] = p_thread.progress
#Thread-Signale mit GuiApp-Funktionen verbinden
QtCore.QObject.connect(p_thread, QtCore.SIGNAL('progressUpdated(str)'), self.updateProgressObject(thread_name))
QtCore.QObject.connect(p_thread, QtCore.SIGNAL('resultsReady(str)'), self.updateResultsObject(thread_name))
def updateProgressObject(self, thread_name):
#Fortschrittsobjekt für alle Threads aktualisieren
self.progress_object[thread_name] = self.processing_threads[thread_name].progress
#Fortschrittsanzeige für ausgewählten Thread aktualisieren
if self.selected_thread == thread_name:
self.setProgressBar(self.progress_object[self.selected_thread])
def updateResultsObject(self, thread_name):
#Ergebnisobjekt für Thread mit Ergebnissen aktualisieren
self.results_object[thread_name] = self.processing_threads[thread_name].getResults()
#Ergebnis-Widget für ausgewählten Thread aktualisieren
try:
self.setResultsWidget(self.results_object[thread_name])
except KeyError:
self.setResultsWidget(None)
Jegliche Kommentare zu diesem Ansatz (z.B. Nachteile, Fehler, Lob usw.) werden geschätzt.
Auflösung:
Ich habe letztendlich die QThread-Klasse und zugehörige Signale und Slots verwendet, um zwischen den Threads zu kommunizieren. Dies liegt hauptsächlich daran, dass mein Programm bereits Qt/PyQt4 für die GUI-Objekte/Widgets verwendet. Diese Lösung erforderte auch weniger Änderungen an meinem vorhandenen Code, um sie umzusetzen.
Hier ist ein Link zu einem passenden Qt-Artikel, der erklärt, wie Qt Threads und Signale behandelt, http://www.linuxjournal.com/article/9602. Auszug unten:
Glücklicherweise erlaubt Qt, dass Signale und Slots über Threads hinweg verbunden werden können – solange die Threads ihre eigene Ereignisschleife ausführen. Dies ist eine deutlich sauberere Methode der Kommunikation im Vergleich zum Senden und Empfangen von Ereignissen, da es all das Verwaltung und die Zwischenschritte und Zwischenklassen QEvent vermeidet, die in jeder nicht trivialen Anwendung notwendig werden. Die Kommunikation zwischen Threads wird nun zu einer Sache des Verbindens von Signalen von einem Thread zu den Slots in einem anderen und die Mutexierung und Thread-Sicherheitsprobleme beim Austausch von Daten zwischen Threads werden von Qt behandelt.
Warum ist es notwendig, in jedem Thread, den Sie verbinden möchten, Ereignisschleifen auszuführen? Der Grund hat mit dem Inter-Thread-Kommunikationsmechanismus zu tun, den Qt verwendet, wenn Signale von einem Thread zum Slot eines anderen Threads verbunden werden. Wenn eine solche Verbindung hergestellt wird, wird sie als eine Warteschlangeverbindung bezeichnet. Wenn Signale durch eine Warteschlangeverbindung ausgesendet werden, wird der Slot beim nächsten Ausführen der Ereignisschleife des Zielobjekts aufgerufen. Wenn der Slot stattdessen direkt durch ein Signal aus einem anderen Thread aufgerufen worden wäre, würde dieser Slot im gleichen Kontext wie der aufrufende Thread ausgeführt werden. Normalerweise wäre das nicht das Gewünschte (und erst recht nicht, wenn man eine Datenbankverbindung verwendet, da die Datenbankverbindung nur vom Thread, der sie erstellt hat, verwendet werden kann). Die Warteschlangenverbindung leitet das Signal richtig an das Thread-Objekt weiter und ruft seinen Slot im eigenen Kontext auf, indem es sich am Ereignissystem anhängt. Dies ist genau das, was wir für die Inter-Thread-Kommunikation wollen, bei der einige der Threads Datenbankverbindungen bearbeiten. Der Qt Signal/Slot-Mechanismus ist im Grunde eine Implementierung des oben beschriebenen Inter-Thread-Ereignisweitergabesystems, jedoch mit einer viel saubereren und einfacher zu verwendenden Schnittstelle.
HINWEIS: Auch eliben hat eine gute Antwort, und wenn ich nicht PyQt4 verwenden würde, das die Thread-Sicherheit und Mutexierung behandelt, wäre seine Lösung meine Wahl gewesen.