Sto cercando di fare una cosa abbastanza comune nella mia applicazione PySide GUI: Voglio delegare alcune attività CPU-Intensive a un thread in background in modo che la mia GUI rimanga reattivo e potrebbe anche visualizzare un indicatore di progresso come il calcolo va.PySide/PyQt - Avvio di un thread intensivo della CPU blocca l'intera applicazione
Ecco quello che sto facendo (sto usando PySide 1.1.1 su Python 2.7, Linux x86_64):
import sys
import time
from PySide.QtGui import QMainWindow, QPushButton, QApplication, QWidget
from PySide.QtCore import QThread, QObject, Signal, Slot
class Worker(QObject):
done_signal = Signal()
def __init__(self, parent = None):
QObject.__init__(self, parent)
@Slot()
def do_stuff(self):
print "[thread %x] computation started" % self.thread().currentThreadId()
for i in range(30):
# time.sleep(0.2)
x = 1000000
y = 100**x
print "[thread %x] computation ended" % self.thread().currentThreadId()
self.done_signal.emit()
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
self.work_thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.work_thread)
self.work_thread.started.connect(self.worker.do_stuff)
self.worker.done_signal.connect(self.work_done)
def initUI(self):
self.btn = QPushButton('Do stuff', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
self.btn.clicked.connect(self.execute_thread)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.show()
def execute_thread(self):
self.btn.setEnabled(False)
self.btn.setText('Waiting...')
self.work_thread.start()
print "[main %x] started" % (self.thread().currentThreadId())
def work_done(self):
self.btn.setText('Do stuff')
self.btn.setEnabled(True)
self.work_thread.exit()
print "[main %x] ended" % (self.thread().currentThreadId())
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
L'applicazione visualizza una singola finestra con un pulsante. Quando si preme il pulsante, mi aspetto che si disattivi mentre viene eseguito il calcolo. Quindi, il pulsante dovrebbe essere riattivato.
Quello che succede, invece, è che quando premo il pulsante l'intera finestra si blocca mentre il calcolo va e poi, quando è finito, riprendo il controllo dell'applicazione. Il pulsante non sembra mai disabilitato. Una cosa divertente che ho notato è che se sostituisco il calcolo intensivo della CPU in do_stuff()
con un semplice time.sleep() il programma si comporta come previsto.
Non so esattamente cosa sta succedendo, ma sembra che la priorità del secondo thread sia così alta da impedire effettivamente che il thread della GUI venga programmato. Se il secondo thread va in stato BLOCCATO (come accade con un sleep()
), la GUI ha effettivamente la possibilità di eseguire e aggiornare l'interfaccia come previsto. Ho provato a cambiare la priorità del thread di lavoro, ma sembra che non possa essere fatto su Linux.
Inoltre, provo a stampare gli ID di thread, ma non sono sicuro se lo sto facendo correttamente. Se lo sono, l'affinità del thread sembra essere corretta.
Ho anche provato il programma con PyQt e il comportamento è esattamente lo stesso, quindi tag e titolo. Se riesco a farlo funzionare con PyQt4 anziché PySide, potrei passare l'intera applicazione a PyQt4
Ho provato a inserire 'time.sleep (0)' al posto del commento 'time.sleep (0.2)' nell'esempio e, sfortunatamente, non risolve il problema. Ho notato che con valori come "time.sleep (0.05)" il pulsante si comporta come previsto. Per ovviare al problema, potrei impostare l'operatore affinché riferisca l'avanzamento solo alcune volte al secondo e dorma per consentire alla GUI di aggiornarsi. – user1491306
Esistono soluzioni alternative per i thread e le GUI (ad esempio http://code.activestate.com/recipes/578154). –
Ho finito per fare così: metto un 'sleep' dove inizia il calcolo, e ogni volta che l'indicatore di avanzamento viene aggiornato, chiamo' app.processEvents() 'per far funzionare un pulsante" Abort ". – user1491306