Question

Questions:

  1. Quelle est la meilleure pratique pour garder une trace de ce une bande de roulement progrès sans verrouillage de l'interface ( "Ne répond pas")?
  2. En général, quelles sont les meilleures pratiques pour threading comme il applique à l'interface graphique développement?

Question de fond:

  • J'ai une interface graphique PyQt pour Windows.
  • Il est utilisé pour traiter des ensembles de HTML documents.
  • Il faut de trois secondes à trois heures pour traiter un ensemble de documents.
  • Je veux être en mesure de traiter plusieurs jeux en même temps.
  • Je ne veux pas l'interface graphique pour verrouiller.
  • Je suis à la recherche sur le module de filetage pour y parvenir.
  • Je suis relativement nouveau à filetage.
  • L'interface graphique a une barre de progression.
  • Je veux afficher la progression de le fil sélectionné.
  • Afficher les résultats du sélectionné fil si elle est terminée.
  • J'utilise Python 2.5.

Mon Idée: Demandez aux fils émettre un QtSignal lorsque le progrès est mis à jour qui déclenche une fonction qui met à jour la barre de progression. signaler également lorsque le traitement terminé si les résultats peuvent être affichés.

#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)

Tout commentaire relatif à cette approche (par exemple inconvénients, pièges, louanges, etc.) seront appréciés.

Résolution:

I fini en utilisant la classe QThread et de signaux associés et des fentes pour la communication entre threads. Ceci est principalement parce que mon programme utilise déjà Qt / PyQt4 pour les objets GUI / widgets. Cette solution doit également moins de changements à mon code existant pour mettre en œuvre.

Voici un lien vers un article Qt applicable qui explique comment threads gère Qt et signaux, http: // www.linuxjournal.com/article/9602 . Extrait ci-dessous:

  

Heureusement, Qt permet   signaux et des fentes à raccorder   à travers des fils-aussi longtemps que les fils   sont en cours d'exécution de leurs propres boucles d'événements.   Cette méthode est beaucoup plus propre de   communication par rapport à l'envoi et   la réception d'événements, car elle évite   toute la comptabilité et intermédiaire   classes dérivées QEvent qui deviennent   nécessaire dans toute non triviale   application. communiquer entre   fils devient maintenant une question de   connecter les signaux d'un fil à   les fentes de l'autre, et le mutexing   et les questions d'échange fil de sécurité   données entre les fils sont traités par   Qt.

     

Pourquoi est-il nécessaire d'exécuter un événement   boucle dans chaque fil auquel vous   veulent connecter les signaux? La raison   doit faire avec l'inter-thread   mécanisme de communication utilisé par Qt   lors de la connexion des signaux provenant d'une   le fil dans la fente d'un autre fil.   Lorsqu'une telle connexion est établie, il est   appelé une connexion en file d'attente.   Lorsque les signaux sont émis à travers un   connexion en file d'attente, la fente est invoquée   la prochaine fois de l'objet de destination   boucle d'événements est exécutée. Si la fente   avait plutôt été invoquée directement par un   signal provenant d'un autre fil, cette fente   exécuterait dans le même contexte que   le fil d'appel. Normalement, cela est   pas ce que vous voulez (et surtout pas   ce que vous voulez si vous utilisez un   connexion de base de données, la base de données   connexion peut être utilisé que par le   fil qui a créé). la file d'attente   connexion envoie correctement la   signaler à l'objet de fil et   invoque son emplacement dans son contexte par   -Support porcin sur le système d'événements.   C'est précisément ce que nous voulons   communication inter-thread dans lequel   certains des fils sont la manipulation   connexions de base de données. Qt   Mécanisme de signal / de la fente est à une racine   la mise en oeuvre de l'inter-thread   système de passage de l'événement décrit ci-dessus,   mais avec beaucoup plus propre et   l'interface plus facile à utiliser.

Remarque: eliben a aussi une bonne réponse, et si je ne l'utilise PyQt4, qui gère la sécurité des threads et mutexage, sa solution aurait été mon choix.

Était-ce utile?

La solution

Si vous voulez utiliser des signaux pour indiquer la progression du thread principal alors vous devriez vraiment utiliser la classe QThread PyQt au lieu de la classe de fil à partir du module de filetage de Python.

Un exemple simple qui utilise QThread, signaux et slots se trouve sur le PyQt Wiki:

https://wiki.python.org/moin/PyQt/Threading,_Signals_and_Slots

Autres conseils

files d'attente python natif ne fonctionnera pas parce que vous devez bloquer sur get () de file d'attente, qui bondes votre interface utilisateur.

Qt met en oeuvre essentiellement un système de file d'attente à l'intérieur pour la communication de fil croix. Essayez cet appel de tout fil pour faire un appel à une fente.

QtCore.QMetaObject.invokeMethod ()

Il est maladroit et mal documenté, mais il doit faire ce que vous voulez même d'un fil de non-Qt.

Vous pouvez également utiliser des machines d'événement pour cela. Voir QApplication (ou QCoreApplication) pour une méthode nommée quelque chose comme "post".

Edit: Voici un exemple plus complet ...

J'ai créé ma propre classe basée sur QWidget. Il a une fente qui accepte une chaîne; Je définis comme ceci:

@QtCore.pyqtSlot(str)
def add_text(self, text):
   ...

Plus tard, je crée une instance de ce widget dans le fil de l'interface principale. A partir du thread principal GUI ou tout autre fil (touchons du bois) Je peux appeler:

QtCore.QMetaObject.invokeMethod(mywidget, "add_text", QtCore.Q_ARG(str,"hello world"))

Maladroit, mais il vous arrive.

Dan.

Je vous recommande d'utiliser la file d'attente au lieu de la signalisation. Personnellement, je trouve beaucoup plus robuste et compréhensible façon de la programmation, car il est plus synchrone.

Threads devraient obtenir « emplois » à partir d'une file d'attente et remettre les résultats sur une autre file d'attente. Une troisième file d'attente peut être utilisé par les fils pour les notifications et messages, comme des erreurs et des « rapports d'étape ». Une fois que vous structurez votre code de cette façon, il devient beaucoup plus simple à gérer.

De cette façon, une seule « file d'attente d'emploi » et « résultat de file d'attente » peuvent également être utilisés par un groupe de threads de travail, il achemine toutes les informations des fils dans le fil de l'interface principale.

Si votre méthode « processDoc » ne change pas d'autres données (pour certaines regarde juste les données et le retourner et ne changent pas les variables ou les propriétés de la classe parent), vous pouvez utiliser Py_BEGIN_ALLOW_THREADS et Py_END_ALLOW_THREADS macroses ( voir ici pour plus de détails ) en elle. Ainsi, le document sera traité en fil qui ne se verrouille pas l'interprète et l'interface utilisateur sera mis à jour.

Vous allez toujours avoir ce problème en Python. Google GIL "verrou global interpretor" pour plus d'arrière-plan. Il y a deux façons généralement recommandées pour contourner le problème que vous rencontrez: utilisez Twisted , ou utiliser un module similaire à la multitraitement introduite dans le module 2.5.

Twisted exigera que vous apprendrez les techniques de programmation asynchrone qui peut être source de confusion au début, mais sera utile si vous avez besoin d'écrire des applications de réseau à haut débit et sera plus bénéfique pour vous à long terme.

Le module multiprocessing se divisera un nouveau processus et utilise IPC pour le faire se comporter comme si vous aviez vrai filetage. Seul inconvénient est que vous auriez besoin Python 2.5 installé, ce qui est assez nouveau et inst » inclus dans la plupart des distributions Linux ou Mac OS X par défaut.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top