È necessario consentire che gli eventi vengano elaborati mentre il ciclo è in esecuzione in modo che l'applicazione possa rimanere reattiva.
Ancora più importante, per le attività a esecuzione prolungata, è necessario fornire un modo per l'utente di interrompere il ciclo una volta avviato.
Un modo semplice per eseguire questa operazione è avviare il ciclo con un timer, quindi chiamare periodicamente lo qApp.processEvents mentre il ciclo è in esecuzione.
Ecco uno script demo che fa questo:
import sys, time
from PyQt4 import QtGui, QtCore
class ProgressBar(QtGui.QWidget):
def __init__(self, parent=None, total=20):
super(ProgressBar, self).__init__(parent)
self.progressbar = QtGui.QProgressBar()
self.progressbar.setMinimum(1)
self.progressbar.setMaximum(total)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
main_layout = QtGui.QGridLayout()
main_layout.addWidget(self.button, 0, 0)
main_layout.addWidget(self.progressbar, 0, 1)
self.setLayout(main_layout)
self.setWindowTitle('Progress')
self._active = False
def handleButton(self):
if not self._active:
self._active = True
self.button.setText('Stop')
if self.progressbar.value() == self.progressbar.maximum():
self.progressbar.reset()
QtCore.QTimer.singleShot(0, self.startLoop)
else:
self._active = False
def closeEvent(self, event):
self._active = False
def startLoop(self):
while True:
time.sleep(0.05)
value = self.progressbar.value() + 1
self.progressbar.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or
value >= self.progressbar.maximum()):
break
self.button.setText('Start')
self._active = False
app = QtGui.QApplication(sys.argv)
bar = ProgressBar(total=101)
bar.show()
sys.exit(app.exec_())
UPDATE
Supponendo che si sta utilizzando l'implementazione in C di Python (cioè CPython), la soluzione a questo problema dipende tutto sulla natura delle attività che devono essere eseguite contemporaneamente alla GUI. Più fondamentalmente, è determinato da CPython che ha un Global Interpreter Lock (GIL).
Non ho intenzione di tentare alcuna spiegazione del GIL di CPython: invece, mi limiterò a raccomandare di guardare questo eccellente PyCon video di Dave Beazley, e lasciarlo a quello.
In genere, quando si cerca di eseguire una GUI in concomitanza con attività in background, la prima domanda da porsi è: è il compito IO-bound, o CPU-bound?
Se è legato all'IO (ad esempio, l'accesso al file system locale, il download da Internet, ecc.), La soluzione è in genere piuttosto semplice, poiché CPython rilascia sempre il GIL per le operazioni di I/O. L'attività in background può essere semplicemente eseguita asynchronously o eseguita da worker thread e non è necessario eseguire alcuna operazione speciale per mantenere la GUI reattiva.
Le principali difficoltà si verificano con i task associati alla CPU, quando c'è una seconda domanda da porsi: l'attività può essere suddivisa in una serie di piccoli passaggi?
Se possibile, la soluzione è inviare periodicamente richieste al thread della GUI per elaborare lo stack corrente di eventi in sospeso. Lo script demo qui sopra è un esempio grezzo di questa tecnica. Più di solito, l'attività verrebbe eseguita in un thread di lavoro separato, che emetterebbe un segnale di aggiornamento gui al completamento di ogni fase dell'attività. (NB: è importante assicurarsi che il thread di lavoro non tenti mai alcuna operazione relativa alla GUI).
Ma se l'attività non può essere suddivisa in piccoli passaggi, quindi nessuna delle solite soluzioni di tipo threading funzionerà. La GUI si bloccherà solo fino al completamento dell'attività, indipendentemente dal fatto che i thread vengano utilizzati o meno.
Per questo ultimo scenario, l'unica soluzione è quella di utilizzare un separato processo, piuttosto che un thread separato - cioè fare uso del modulo multiprocessing. Naturalmente, questa soluzione sarà efficace solo se il sistema di destinazione ha più core CPU disponibili. Se c'è un solo core della CPU con cui giocare, non c'è praticamente nulla che possa essere fatto per aiutare (oltre a passare a una diversa implementazione di Python, o ad una lingua completamente diversa).
Se eseguo questo, la finestra della GUI si blocca anche con l'errore "Non risponde, forza chiudi". Tuttavia, se aspetto che tutti i miei compiti siano terminati, la normale applicazione continua. – Tuim
@Tuim. Il mio script è solo una semplice demo basata sul codice della tua domanda. Non è una soluzione universale che funzionerà in tutte le situazioni. Devi aggiornare la tua domanda con una spiegazione adeguata di ciò che stai cercando di fare. Quali sono questi "compiti" che menzioni? Sono vincolati alla CPU o legati all'IO? L'applicazione che svolge i compiti che hai scritto tu stesso può quindi essere modificata? In che lingua è scritto? Ecc. Ecc. – ekhumoro
Queste attività sono installazioni, ad esempio decompressione di file zip, installazione di pacchetti msi/deb in cose del genere. Ma questo non è molto rilevante per il caso. L'applicazione è scritta in Python e completamente personalizzabile. Anche io non mi aspetto una risposta in grado di copiare e incollare! Mi aspetto un suggerimento nella giusta direzione e quello che hai non sembra essere la giusta direzione per me, ci ho provato. Senza offesa. – Tuim