Come sottolinea Nathan W , il modo per farlo è con il multithreading, ma la sottoclasse di QThread non è la migliore pratica. Vedi qui: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
Vedi sotto un esempio di come creare un QObject
, quindi spostarlo in un QThread
(cioè il modo "corretto" per farlo). Questo esempio calcola l'area totale di tutte le funzionalità in un livello vettoriale (usando la nuova API QGIS 2.0!).
Innanzitutto, creiamo l'oggetto "lavoratore" che farà il lavoro pesante per noi:
class Worker(QtCore.QObject):
def __init__(self, layer, *args, **kwargs):
QtCore.QObject.__init__(self, *args, **kwargs)
self.layer = layer
self.total_area = 0.0
self.processed = 0
self.percentage = 0
self.abort = False
def run(self):
try:
self.status.emit('Task started!')
self.feature_count = self.layer.featureCount()
features = self.layer.getFeatures()
for feature in features:
if self.abort is True:
self.killed.emit()
break
geom = feature.geometry()
self.total_area += geom.area()
self.calculate_progress()
self.status.emit('Task finished!')
except:
import traceback
self.error.emit(traceback.format_exc())
self.finished.emit(False, self.total_area)
else:
self.finished.emit(True, self.total_area)
def calculate_progress(self):
self.processed = self.processed + 1
percentage_new = (self.processed * 100) / self.feature_count
if percentage_new > self.percentage:
self.percentage = percentage_new
self.progress.emit(self.percentage)
def kill(self):
self.abort = True
progress = QtCore.pyqtSignal(int)
status = QtCore.pyqtSignal(str)
error = QtCore.pyqtSignal(str)
killed = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal(bool, float)
Per usare il lavoratore dobbiamo inizializzarlo con un livello vettoriale, spostarlo sul thread, collegare alcuni segnali, quindi avviarlo. Probabilmente è meglio guardare il blog linkato sopra per capire cosa sta succedendo qui.
thread = QtCore.QThread()
worker = Worker(layer)
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.progress.connect(self.ui.progressBar)
worker.status.connect(iface.mainWindow().statusBar().showMessage)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
worker.finished.connect(thread.quit)
thread.start()
Questo esempio illustra alcuni punti chiave:
- Tutto all'interno del
run()
metodo del lavoratore è all'interno di un'istruzione try-tranne. È difficile ripristinare quando il codice si arresta in modo anomalo all'interno di un thread. Emette il traceback tramite il segnale di errore, che di solito mi connetto al QgsMessageLog
.
- Il segnale finito indica al metodo collegato se il processo è stato completato correttamente, nonché il risultato.
- Il segnale di avanzamento viene chiamato solo quando cambia la percentuale completa, anziché una volta per ogni funzione. Questo impedisce troppe chiamate per aggiornare la barra di avanzamento rallentando il processo di lavoro, il che vanificherebbe l'intero punto di esecuzione del lavoratore in un altro thread: separare il calcolo dall'interfaccia utente.
- Il lavoratore implementa un
kill()
metodo che consente alla funzione di terminare con grazia. Non provare a utilizzare il terminate()
metodo QThread
: potrebbero accadere cose brutte!
Assicurati di tenere traccia del tuo thread
e degli worker
oggetti da qualche parte nella struttura del plugin. Qt si arrabbia se non lo fai. Il modo più semplice per farlo è salvarli nella finestra di dialogo quando li crei, ad esempio:
thread = self.thread = QtCore.QThread()
worker = self.worker = Worker(layer)
Oppure puoi lasciare che Qt diventi proprietario di QThread:
thread = QtCore.QThread(self)
Mi ci è voluto molto tempo per scavare tutti i tutorial per mettere insieme questo modello, ma da allora lo sto riutilizzando dappertutto.