Frage

Fragen:

  1. Was ist die beste Praxis für die Verfolgung der einer Lauffläche Fortschritt ohne die GUI Verriegelungs ( "Reagiert nicht")?
  2. 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.

War es hilfreich?

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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top