2013-06-02 10 views
23

Sto cercando di imparare come usare QThreads in un'applicazione PyQt Gui. Ho cose che funzionano per un po ', con (di solito) punti in cui posso aggiornare un Gui, ma vorrei dividere il lavoro principale con il suo thread (a volte la roba si blocca, e sarebbe bello avere un Annulla/riprova il pulsante, che ovviamente non funziona se il Gui è bloccato perché il Loop principale è bloccato).Esempio del modo giusto di usare QThread in PyQt?

Ho letto https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/. Quella pagina dice che la reimplementazione del metodo run non è il modo per farlo. Il problema che sto avendo è trovare un esempio PyQt che ha un thread principale che fa il Gui e un thread di lavoro che non lo fa in questo modo. Il post del blog è per C++, quindi mentre gli esempi aiutano, sono ancora un po 'perso. Qualcuno può per favore indicarmi un esempio del modo giusto per farlo in Python?

+4

Questo appare come un duplicato di [thread in background con QThread in PyQt] (http://stackoverflow.com/questions/6783194/background-thread-with-qthread-in-pyqt). Il secondo esempio nella risposta accettata sembra una semplice traduzione del codice C++ dal blog che hai collegato. – abarnert

+0

Inoltre, hai scritto codice di threading Python nativo (con 'threading.Thread', ecc.)? In caso contrario, potresti voler esaminare alcuni esempi di ciò prima. (Vedi anche [Applicazione di thread in un'applicazione PyQt: utilizza thread Qt o thread Python] (http://stackoverflow.com/questions/1595649/threading-in-a-pyqt-application-use-qt-threads-or-python- thread) per vedere se hai bisogno di QThread qui.) – abarnert

+0

@abarnert Grazie, penso che il collegamento fosse proprio quello che stavo cercando. Avevo visto il secondo link e ho deciso di utilizzare QThreads perché volevo essere in grado di inviare slot/segnali tra i thread. Sapevo che 'threading.Thread' esiste, ma non l'ho mai usato prima. Ho fatto molte ricerche e ho persino visto il primo link, l'ho scremato, ho visto 'def run' e ho proseguito, non rendendomi conto di aver mostrato entrambi i modi! – Azendale

risposta

7

Ecco un esempio funzionante di un thread di lavoro separato che può inviare e ricevere segnali per consentirgli di comunicare con una GUI.

Ho creato due pulsanti semplici, uno che avvia un lungo calcolo in un thread separato e uno che termina immediatamente il calcolo e reimposta il thread di lavoro.

Terminare forzatamente un thread come si fa qui non è generalmente il modo migliore di fare le cose, ma ci sono situazioni in cui uscire sempre con garbo non è un'opzione.

from PyQt4 import QtGui, QtCore 
import sys 
import random 

class Example(QtCore.QObject): 

    signalStatus = QtCore.pyqtSignal(str) 

    def __init__(self, parent=None): 
     super(self.__class__, self).__init__(parent) 

     # Create a gui object. 
     self.gui = Window() 

     # Setup the worker object and the worker_thread. 
     self.worker = WorkerObject() 
     self.worker_thread = QtCore.QThread() 
     self.worker.moveToThread(self.worker_thread) 
     self.worker_thread.start() 

     # Make any cross object connections. 
     self._connectSignals() 

     self.gui.show() 

    def _connectSignals(self): 
     self.gui.button_start.clicked.connect(self.worker.startWork) 
     self.gui.button_cancel.clicked.connect(self.forceWorkerReset) 
     self.signalStatus.connect(self.gui.updateStatus) 
     self.worker.signalStatus.connect(self.gui.updateStatus) 

     self.parent().aboutToQuit.connect(self.forceWorkerQuit) 

    def forceWorkerReset(self): 
     if self.worker_thread.isRunning(): 
      self.worker_thread.terminate() 
      self.worker_thread.wait() 

      self.signalStatus.emit('Idle.') 
      self.worker_thread.start() 

    def forceWorkerQuit(self): 
     if self.worker_thread.isRunning(): 
      self.worker_thread.terminate() 
      self.worker_thread.wait() 


class WorkerObject(QtCore.QObject): 

    signalStatus = QtCore.pyqtSignal(str) 

    def __init__(self, parent=None): 
     super(self.__class__, self).__init__(parent) 

    @QtCore.pyqtSlot()   
    def startWork(self): 
     for ii in range(7): 
      number = random.randint(0,5000**ii) 
      self.signalStatus.emit('Iteration: {}, Factoring: {}'.format(ii, number)) 
      factors = self.primeFactors(number) 
      print('Number: ', number, 'Factors: ', factors) 
     self.signalStatus.emit('Idle.') 

    def primeFactors(self, n): 
     i = 2 
     factors = [] 
     while i * i <= n: 
      if n % i: 
       i += 1 
      else: 
       n //= i 
       factors.append(i) 
     if n > 1: 
      factors.append(n) 
     return factors 


class Window(QtGui.QWidget): 

    def __init__(self): 
     QtGui.QWidget.__init__(self) 
     self.button_start = QtGui.QPushButton('Start', self) 
     self.button_cancel = QtGui.QPushButton('Cancel', self) 
     self.label_status = QtGui.QLabel('', self) 

     layout = QtGui.QVBoxLayout(self) 
     layout.addWidget(self.button_start) 
     layout.addWidget(self.button_cancel) 
     layout.addWidget(self.label_status) 

     self.setFixedSize(400, 200) 

    @QtCore.pyqtSlot(str) 
    def updateStatus(self, status): 
     self.label_status.setText(status) 


if __name__=='__main__': 
    app = QtGui.QApplication(sys.argv) 
    example = Example(app) 
    sys.exit(app.exec_()) 
+0

Quando eseguo questo, ottengo. 'Qt ha catturato un'eccezione generata da un gestore di eventi. Lanciare le eccezioni da un gestore di eventi non è supportato in Qt. È necessario reimplementare QApplication :: notify() e rilevare tutte le eccezioni. " –

+0

Qualche idea di cosa fosse l'eccezione? Ho provato questo snippet usando PyQt4 con Python 2.7 su OS X senza errori. Quando provo a usarlo con Python 3.4, occasionalmente ottengo errori di segmentazione. Non sono sicuro che questo problema riguardi il codice scritto o l'implementazione specifica (specifica della versione). – amicitas

+0

Non ho indagato su cosa sta lanciando ma a quanto pare hai qualcosa che occasionalmente genera un'eccezione in un loop di messaggi, che non deve mai accadere. –

0

Hai ragione che è una buona cosa avere un thread di lavoro durante l'elaborazione mentre il thread principale sta facendo la GUI. Inoltre, PyQt fornisce strumenti di thread con un meccanismo di segnale/slot che è thread-safe.

This may sound of interest. Nel loro esempio, costruiscono una GUI

import sys, time 
from PyQt4 import QtCore, QtGui 

class MyApp(QtGui.QWidget): 
def __init__(self, parent=None): 
    QtGui.QWidget.__init__(self, parent) 

    self.setGeometry(300, 300, 280, 600) 
    self.setWindowTitle('threads') 

    self.layout = QtGui.QVBoxLayout(self) 

    self.testButton = QtGui.QPushButton("test") 
    self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test) 
    self.listwidget = QtGui.QListWidget(self) 

    self.layout.addWidget(self.testButton) 
    self.layout.addWidget(self.listwidget) 

def add(self, text): 
    """ Add item to list widget """ 
    print "Add: " + text 
    self.listwidget.addItem(text) 
    self.listwidget.sortItems() 

def addBatch(self,text="test",iters=6,delay=0.3): 
    """ Add several items to list widget """ 
    for i in range(iters): 
    time.sleep(delay) # artificial time delay 
    self.add(text+" "+str(i)) 

def test(self): 
    self.listwidget.clear() 
    # adding entries just from main application: locks ui 
    self.addBatch("_non_thread",iters=6,delay=0.3) 

(ui Simpel contenente un widget lista che aggiungeremo alcuni elementi facendo clic su un pulsante)

Si può quindi creare la nostra classe thread, un exemple è

class WorkThread(QtCore.QThread): 
def __init__(self): 
    QtCore.QThread.__init__(self) 

def __del__(self): 
    self.wait() 

def run(self): 
    for i in range(6): 
    time.sleep(0.3) # artificial time delay 
    self.emit(QtCore.SIGNAL('update(QString)'), "from work thread " + str(i)) 

    self.terminate() 

che fai ridefinire il metodo run(). È possibile trovare un'alternativa a terminate(), consultare l'esercitazione.

+9

L'OP ha detto esplicitamente che vuole usare il meccanismo 'moveToThread', piuttosto che il meccanismo' QThread.run'. Non sono sicuro che abbia una buona ragione per questo, ma comunque non stai rispondendo alla sua domanda. – abarnert

+1

L'OP ha ragione: non dovresti sottoclasse QThread. Vedi http: //blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/ – blokeley

+0

[I documenti Qt sul threading] (http://doc.qt.io/qt-4.8/thread-basics.html#gui-thread -and-worker-thread) menzionare la sottoclasse QThread anche se ... –