PySide / PyQt - Iniciando um thread intensivo de CPU trava o aplicativo inteiro

Eu estou tentando fazer uma coisa bastante comum no meu aplicativo GUI PySide: Eu quero delegar algumas tarefas intensivas de CPU para um segmento de plano de fundo para que minha GUI permanece responsivo e poderia até exibir um indicador de progresso como o cálculo vai.

Aqui está o que estou fazendo (estou usando o PySide 1.1.1 no 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()

O aplicativo exibe uma única janela com um botão. Quando o botão é pressionado, espero que ele se desative enquanto a computação é executada. Em seguida, o botão deve ser reativado.

O que acontece, em vez disso, é que quando pressiono o botão, toda a janela congela enquanto a computação avança e, quando termina, eu recupero o controle do aplicativo. O botão nunca parece estar desativado. Uma coisa engraçada que notei é que, se eu substituir a computação intensiva da CPU emdo_stuff() com um simples time.sleep () o programa se comporta como esperado.

Eu não sei exatamente o que está acontecendo, mas parece que a prioridade do segundo thread é tão alta que na verdade está impedindo que o thread da GUI seja agendado. Se o segundo segmento entrar no estado BLOCKED (como acontece com umsleep()), a GUI tem realmente a chance de executar e atualiza a interface conforme o esperado. Eu tentei mudar a prioridade do thread de trabalho, mas parece que isso não pode ser feito no Linux.

Além disso, tento imprimir os IDs de encadeamento, mas não tenho certeza se estou fazendo isso corretamente. Se eu sou, a afinidade de thread parece estar correta.

Eu também tentei o programa com PyQt e o comportamento é exatamente o mesmo, daí as tags e o título. Se eu posso fazê-lo funcionar com o PyQt4 em vez do PySide, eu poderia mudar toda a minha aplicação para o PyQt4

questionAnswers(2)

yourAnswerToTheQuestion