Domanda

Domande:

  1. Qual è la migliore pratica per Tenere traccia di un battistrada progredire senza bloccare la GUI ("Non risponde")?
  2. In generale, quali sono le migliori pratiche per threading come si applica alla GUI sviluppo?

Contesto della domanda:

  • Ho una GUI PyQt per Windows.
  • Viene utilizzato per elaborare insiemi di HTML Documenti.
  • Ci vogliono da tre secondi a tre ore per elaborare una serie di Documenti.
  • Voglio essere in grado di elaborare più set contemporaneamente.
  • Non voglio che la GUI si blocchi.
  • Sto guardando il modulo di filettatura per raggiungere questo obiettivo.
  • Sono relativamente nuovo al threading.
  • La GUI ha una barra di avanzamento.
  • Voglio che mostri lo stato di avanzamento di il thread selezionato.
  • Visualizza i risultati del prodotto selezionato filettare se è finito.
  • Sto usando Python 2.5.

La mia idea: Chiedi ai thread di emettere un QtSignal quando l'avanzamento viene aggiornato che attiva alcune funzioni che aggiorna la barra di avanzamento.Segnala inoltre il termine dell'elaborazione in modo che i risultati possano essere visualizzati.

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

Qualsiasi commento su questo approccio (ad es.inconvenienti, insidie, elogi, ecc.) saranno apprezzati.

Risoluzione:

Ho finito per utilizzare la classe QThread e i segnali e gli slot associati per comunicare tra i thread.Ciò è dovuto principalmente al fatto che il mio programma utilizza già Qt/PyQt4 per gli oggetti/widget della GUI.Questa soluzione richiedeva inoltre meno modifiche al codice esistente da implementare.

Ecco un collegamento a un articolo Qt applicabile che spiega come Qt gestisce thread e segnali, http://www.linuxjournal.com/article/9602.Estratto di seguito:

Fortunatamente, Qt lo permette segnali e slot da collegare tra i thread, a condizione che i thread stanno eseguendo i propri cicli di eventi.Questo è un metodo molto più pulito di comunicazione rispetto all'invio e alla ricevere eventi, perché evita tutta la contabilità e l'intermediazione Classi derivate da QEvent che diventano necessario in qualsiasi applicazione.Comunicare tra thread ora diventa una questione di Collegamento dei segnali da un thread a le fessure in un altro, e il mutexing e problemi di sicurezza della filettatura dello scambio I dati tra i thread vengono gestiti da Qt.

Perché è necessario organizzare un evento all'interno di ogni thread a cui si aggiunge Vuoi collegare i segnali?Il motivo ha a che fare con l'inter-thread meccanismo di comunicazione utilizzato da Qt quando si collegano segnali da uno filettare alla fessura di un'altra filettatura.Quando viene effettuata una tale connessione, è definita connessione in coda.Quando i segnali vengono emessi attraverso un in coda, lo slot viene richiamato la volta successiva che l'oggetto di destinazione viene eseguito il ciclo di eventi.Se lo slot era stato invece invocato direttamente da un segnale da un altro thread, quello slot verrebbe eseguito nello stesso contesto di Il thread chiamante.Normalmente, questo è non è quello che vuoi (e soprattutto non quello che vuoi se stai usando un connessione al database, in quanto il database connessione può essere utilizzata solo dal thread che l'ha creata).L'accodata connessione invia correttamente il segnale all'oggetto thread e invoca il suo slot nel proprio contesto A sostegno del sistema di eventi.Questo è esattamente ciò che vogliamo comunicazione inter-thread in cui alcuni dei thread sono in fase di gestione connessioni al database.Il Qt Il meccanismo di segnale/slot è alla radice e l'attuazione dell'inter-thread schema di superamento degli eventi descritto sopra, ma con un aspetto molto più pulito e Interfaccia più facile da usare.

NOTA: eliben ha anche una buona risposta e se non avessi utilizzato PyQt4, che gestisce la sicurezza dei thread e il mutex, la sua soluzione sarebbe stata la mia scelta.

È stato utile?

Soluzione

Se desideri utilizzare segnali per indicare l'avanzamento del thread principale, dovresti utilizzare la classe QThread di PyQt invece della classe Thread del modulo threading di Python.

Un semplice esempio che utilizza QThread, segnali e slot può essere trovato sul Wiki PyQt:

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

Altri suggerimenti

Le code Python native non funzioneranno perché devi bloccare la coda get(), che blocca la tua interfaccia utente.

Qt implementa essenzialmente un sistema di code all'interno per la comunicazione cross thread.Prova questa chiamata da qualsiasi thread per pubblicare una chiamata in uno slot.

QtCore.QMetaObject.invokeMethod()

È goffo e scarsamente documentato, ma dovrebbe fare quello che vuoi anche da un thread non Qt.

A questo scopo è anche possibile utilizzare macchinari per eventi.Vedi QApplication (o QCoreApplication) per un metodo chiamato qualcosa come "post".

Modificare:Ecco un esempio più completo...

Ho creato la mia classe basata su QWidget.Ha uno slot che accetta una stringa;Lo definisco così:

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

Successivamente, creo un'istanza di questo widget nel thread principale della GUI.Dal thread principale della GUI o da qualsiasi altro thread (tocca ferro) posso chiamare:

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

Goffo, ma ti porta lì.

Dan.

Ti consiglio di utilizzare Queue invece di Signaling.Personalmente lo trovo un modo di programmare molto più robusto e comprensibile, perché più sincrono.

I thread dovrebbero ottenere "lavori" da una coda e riportare i risultati su un'altra coda.Ancora una terza coda può essere utilizzata dai thread per notifiche e messaggi, come errori e "rapporti sullo stato di avanzamento".Una volta strutturato il codice in questo modo, diventa molto più semplice da gestire.

In questo modo, una singola "coda di lavoro" e "coda di risultati" possono essere utilizzate anche da un gruppo di thread di lavoro, instradando tutte le informazioni dai thread al thread principale della GUI.

Se il tuo metodo "processDoc" non modifica nessun altro dato (cerca solo alcuni dati e li restituisce e non modifica variabili o proprietà della classe genitore) puoi utilizzare le macro Py_BEGIN_ALLOW_THREADS e Py_END_ALLOW_THREADS ( vedere qui per i dettagli ) dentro.Quindi il documento verrà elaborato in un thread che non bloccherà l'interprete e l'interfaccia utente verrà aggiornata.

Avrai sempre questo problema in Python.Google GIL "blocco interprete globale" per ulteriori informazioni.Esistono due modi generalmente consigliati per aggirare il problema riscontrato:utilizzo Contorto, oppure utilizzare un modulo simile a multielaborazione modulo introdotto nella versione 2.5.

Twisted richiederà l'apprendimento di tecniche di programmazione asincrona che potrebbero creare confusione all'inizio, ma saranno utili se mai avrai bisogno di scrivere app di rete ad alto rendimento e ti saranno più vantaggiose a lungo termine.

Il modulo multiprocessing eseguirà il fork di un nuovo processo e utilizzerà IPC per farlo comportare come se avessi un vero threading.L'unico svantaggio è che avresti bisogno di Python 2.5 installato che è abbastanza nuovo e incluso nella maggior parte delle distribuzioni Linux o OSX per impostazione predefinita.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top