Estoy intentando hacer algo bastante común en mi aplicación PySide GUI: quiero delegar algunas tareas intensivas de CPU en un hilo de fondo para que mi GUI se mantenga receptivo e incluso podría mostrar un indicador de progreso a medida que avanza el cálculo.PySide/PyQt - Iniciar un hilo intensivo de CPU bloquea toda la aplicación
Esto es lo que estoy haciendo (estoy usando PySide 1.1.1 de 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()
La aplicación muestra una sola ventana con un botón. Cuando se presiona el botón, espero que se deshabilite mientras se realiza el cálculo. Entonces, el botón debería ser reactivado.
Lo que ocurre, en cambio, es que cuando presiono el botón toda la ventana se congela mientras el cálculo continúa y luego, cuando termina, recupero el control de la aplicación. El botón nunca parece estar deshabilitado. Una cosa graciosa que noté es que si reemplazo el cálculo intensivo de CPU en do_stuff()
con un simple time.sleep() el programa se comporta como se esperaba.
No sé exactamente qué está pasando, pero parece que la prioridad del segundo subproceso es tan alta que en realidad impide que se programe alguna vez el subproceso de GUI. Si el segundo hilo entra en estado BLOQUEADO (como ocurre con un sleep()
), la GUI tiene la posibilidad de ejecutar y actualizar la interfaz como se esperaba. Traté de cambiar la prioridad del subproceso de trabajo, pero parece que no se puede hacer en Linux.
Además, trato de imprimir los ID de hilo, pero no estoy seguro de si lo estoy haciendo correctamente. Si lo estoy, la afinidad del hilo parece ser correcta.
También probé el programa con PyQt y el comportamiento es exactamente el mismo, de ahí las etiquetas y el título. Si puedo hacer que funcione con PyQt4 en lugar de PySide, podría cambiar toda mi aplicación a PyQt4
Intenté poner 'time.sleep (0)' en lugar del comentario 'time.sleep (0.2)' en el ejemplo y, desafortunadamente, no soluciona el problema. Noté que con valores como 'time.sleep (0.05)' el botón se comporta como se esperaba. Como solución alternativa, podría configurar el trabajador para informar el progreso solo unas pocas veces por segundo y dormir para permitir que la GUI se actualice solo. – user1491306
Hay soluciones para subprocesos y GUI (como http://code.activestate.com/recipes/578154). –
Terminé haciendo esto: puse un 'sleep' donde comienza el cálculo, y cada vez que el indicador de progreso se actualiza, llamo a' app.processEvents() 'para hacer que funcione un botón" Abort ". – user1491306