Wie verfolgen Thread Fortschritte in Python zu halten, ohne die PyQt GUI Einfrieren?
-
05-09-2019 - |
Frage
Fragen:
- Was ist die beste Praxis für die Verfolgung der einer Lauffläche Fortschritt ohne die GUI Verriegelungs ( "Reagiert nicht")?
- Im Allgemeinen was sind die besten Praktiken für Einfädeln, wie es GUI gilt Entwicklung?
Frage Hintergrund:
- Ich habe eine PyQt GUI für Windows.
- Es wird verwendet Sätze von HTML zu verarbeiten Dokumente.
- Es dauert überall von 3 Sekunden bis drei Stunden einen Satz zu verarbeiten, Dokumente.
- Ich möchte verarbeiten können mehrere Sätze zur gleichen Zeit.
- Ich möchte nicht, die GUI zu sperren.
- Ich betrachte das Threading-Modul um dies zu erreichen.
- Ich bin relativ neu in Threading.
- Die GUI hat einen Fortschrittsbalken.
- Ich will es den Fortschritt anzuzeigen der ausgewählte Thread.
- Anzeige Ergebnisse des ausgewählten Faden, wenn es fertig ist.
- Ich verwende Python 2.5.
Meine Idee: Haben die Fäden eine QtSignal emittieren, wenn der Fortschritt aktualisiert wird, die eine Funktion auslöst, die den Fortschrittsbalken aktualisiert. Auch das Signal, wenn Sie fertig Verarbeitung so können die Ergebnisse angezeigt werden.
#NOTE: this is example code for my idea, you do not have
# to read this to answer the question(s).
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 between 0 and 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)
#emit signal only if progress has changed
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):
''' this is tivial for shortness sake '''
return re.findall('<a [^>]*>.*?</a>', 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):
#create new thread
p_thread = ProcessingThread(docs)
thread_name = "example_thread_name"
p_thread.setName(thread_name)
p_thread.start()
#add thread to dict of threads
self.processing_threads[thread_name] = p_thread
#init progress_object for this thread
self.progress_object[thread_name] = p_thread.progress
#connect thread signals to GuiApp functions
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):
#update progress_object for all threads
self.progress_object[thread_name] = self.processing_threads[thread_name].progress
#update progress bar for selected thread
if self.selected_thread == thread_name:
self.setProgressBar(self.progress_object[self.selected_thread])
def updateResultsObject(self, thread_name):
#update results_object for thread with results
self.results_object[thread_name] = self.processing_threads[thread_name].getResults()
#update results widget for selected thread
try:
self.setResultsWidget(self.results_object[thread_name])
except KeyError:
self.setResultsWidget(None)
Jeder Kommentar zu diesem Ansatz (z Nachteile, Fallen, Lob, etc.) wird geschätzt.
Auflösung:
beendet I zwischen Threads mit der QThread Klasse und zugehörigen Signale und die Schlitze bis zu kommunizieren. Dies ist in erster Linie, weil mein Programm bereits verwendet Qt / PyQt4 für das GUI-Objekte / Widgets. Diese Lösung auch weniger Änderungen an meinen vorhandenen Code erforderlich umzusetzen.
Hier ist ein Link zu einem anwendbaren Qt Artikel, der erklärt, wie Qt behandelt Themen und Signale, http: // www.linuxjournal.com/article/9602 . Auszug unten:
Zum Glück, Qt erlaubt Signale und Schlitzen verbunden werden über Gewinde-solange die Gewinde ihre eigenen Ereignisschleifen ausgeführt werden. Dies ist ein viel sauberes Verfahren Kommunikation im Vergleich zum Senden und Ereignisse empfangen, weil sie vermeidet all die Buchhaltung und Zwischen QEvent abgeleiteten Klassen, die sich notwendig in jedem nicht-trivialen Anwendung. Die Kommunikation zwischen Fäden wird nun eine Selbst Verbindungssignale von einem Thread zu die Schlitze in der anderen, und die mutexing und gewindeSicherheitsFragen des Austauschens Daten zwischen Fäden werden von behandelt Qt.
Warum ist es notwendig, ein Ereignis ausgeführt werden soll innerhalb jeder Fadenschlaufe, an denen Sie wollen Signale verbinden? Der Grund hat mit dem Inter-Thread zu tun Kommunikationsmechanismus verwendet, durch Qt beim Verbinden von Signalen von einer Faden den Schlitz von einem anderen Thread. Wenn eine solche Verbindung hergestellt wird, ist es bezeichnet als eine Warteschlange-Verbindung. Wenn Signale werden durch einen ausgesendeten Verbindung der Warteschlange ist der Schlitz aufgerufen das nächste Mal des Zielobjekt Ereignisschleife wird ausgeführt. Wenn der Schlitz hatte stattdessen direkt durch ein aufgerufen Signal von einem anderen Thread, den Schlitz in diesem Zusammenhang würde ausführen als der aufrufende Thread. Normalerweise ist dies nicht das, was Sie wollen (und schon gar nicht was Sie wollen, wenn Sie eine verwenden Datenbankverbindung, wie die Datenbank Verbindung kann nur durch die verwendet werden, Faden, der sie erstellt). die Warteschlange Verbindung richtig löst das Signal an das Thread-Objekt und in seinem eigenen Kontext ruft seinen Schlitz durch huckepack auf dem Ereignis-System. Das ist genau das, was wir wollen Inter-Thread-Kommunikation, in dem einige der Fäden Handhabungs Datenbankverbindungen. die Qt Signal / Schlitz-Mechanismus ist im Grunde eine Umsetzung des inter-thread Ereignis-passing Schema oben dargelegt, aber mit einem viel sauberen und einfacher zu bedienende Schnittstelle.
Hinweis: eliben auch eine gute Antwort hat, und ob ich mit PyQt4 nicht, der Thread-Sicherheit und mut GriffeExing, seine Lösung würde meine Wahl gewesen.
Lösung
Wenn Sie möchten, Signale verwenden, Fortschritte zu den Haupt-Thread, um anzuzeigen, dann sollten Sie wirklich PyQt der QThread Klasse statt der Klasse Thread aus Python threading Modul werden.
Ein einfaches Beispiel, das QThread, Signale und Slots verwendet, kann auf dem PyQt Wiki zu finden:
https://wiki.python.org/moin/PyQt/Threading,_Signals_and_Slots
Andere Tipps
Native Python Warteschlangen wird nicht funktionieren, weil Sie auf Warteschlange get () haben zu blockieren, Spunde die UI-up.
Qt implementiert im Wesentlichen eine Warteschlange-System auf der Innenseite für Querfaden Kommunikation. Versuchen Sie diesen Anruf von jedem Thread einen Anruf an einen Schlitz zu senden.
QtCore.QMetaObject.invokeMethod ()
Es ist klobig und schlecht dokumentiert ist, aber es sollte tun, was Sie wollen, auch von einem nicht-Qt-Thread.
Sie können auch Ereignis Maschinen für diese. Siehe QApplication (oder QCoreApplication) für eine Methode mit dem Namen etwas wie "post".
Edit: Hier ist ein vollständigeres Beispiel ...
Ich habe meine eigene Klasse basierend auf QWidget. Es hat einen Schlitz, der eine Zeichenfolge annimmt; Ich definiere es wie folgt aus:
@QtCore.pyqtSlot(str)
def add_text(self, text):
...
Später habe ich eine Instanz dieses Widgets im Haupt GUI-Thread erstellen. Vom Haupt GUI-Thread oder einem anderen Thread (Schlag auf Holz) kann ich nennen:
QtCore.QMetaObject.invokeMethod(mywidget, "add_text", QtCore.Q_ARG(str,"hello world"))
Clunky, aber es bekommt man es.
Dan.
Ich empfehle Ihnen Queue zu verwenden, anstatt Signalisierung. Persönlich ich es sehr viel robuster und verständliche Art und Weise der Programmierung, weil es mehr synchron ist.
Themen sollten „Jobs“ aus einer Warteschlange, erhalten und die Ergebnisse auf einem anderen Warteschlange zurückstellen. Noch eine dritte Warteschlange kann durch die Fäden für Benachrichtigungen und Meldungen verwendet werden, wie Fehler und „Fortschrittsberichte“. Sobald Sie Ihren Code auf diese Weise strukturieren, wird es viel einfacher zu verwalten.
Auf diese Weise wird eine einzelne „Job Queue“ und „Ergebnis Queue“ kann auch von einer Gruppe von Arbeitsthreads, es leitet die Informationen alle aus den Fäden in das Haupt GUI-Thread verwendet werden.
Wenn Ihre Methode „processDoc“ keine anderen Daten ändern (sieht nur für einige Daten und senden Sie es und keine Variablen oder Eigenschaften der Elternklasse ändern) können Sie Py_BEGIN_ALLOW_THREADS und Py_END_ALLOW_THREADS macroses ( hier für weitere Details siehe ) darin. So wird das Dokument in Thread verarbeitet werden, die den Interpreter nicht gesperrt werden und UI wird aktualisiert.
Sie werden immer dieses Problem in Python haben. Google GIL "global interpretor lock" für mehr Hintergrund. Es gibt zwei allgemein empfohlenen Wege, um das Problem zu bekommen, die Sie erleben: Verwenden Sie Verdrehte oder ein Modul verwenden ähnlich der Modul in 2.5 eingeführt Multiprocessing.
Verdrehte erfordert, dass Sie asynchrone Programmiertechniken erlernen, die am Anfang verwirrend sein kann, wird aber hilfreich sein, wenn Sie jemals einen hohen Durchsatz Netzwerk-Anwendungen schreiben müssen, und wird mehr von Vorteil für Sie auf lange Sicht.
Das Multiprocessing-Modul wird ein neues Verfahren gabeln und verwendet IPC, um es so, als ob Sie einen echten Threading hatte. Der einzige Nachteil ist, dass man 2,5 Python installiert sein würden, die recht neu und inst ist‘, die in den meisten Linux-Distributionen oder OSX standardmäßig aktiviert.