PySide / PyQt - Uruchomienie wątku intensywnie korzystającego z CPU zawiesza całą aplikację
Próbuję zrobić dość typową rzecz w mojej aplikacji GUI PySide: Chcę delegować niektóre zadania intensywne na CPU do wątku w tle, tak aby mój GUI pozostawał responsywny, a nawet wyświetlał wskaźnik postępu w miarę wykonywania obliczeń.
Oto co robię (używam PySide 1.1.1 na Pythonie 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()
Aplikacja wyświetla pojedyncze okno z przyciskiem. Po naciśnięciu przycisku oczekuję, że sam się wyłączy podczas wykonywania obliczeń. Następnie przycisk powinien zostać ponownie włączony.
Zamiast tego, gdy nacisnę przycisk, całe okno zamarza, podczas gdy obliczenia się kończą, a następnie, gdy jest zakończone, odzyskuję kontrolę nad aplikacją. Przycisk nigdy nie wydaje się być wyłączony. Zabawną rzeczą, jaką zauważyłem, jest to, że jeśli zastąpię intensywne obliczanie procesora wdo_stuff()
z prostym time.sleep () program zachowuje się zgodnie z oczekiwaniami.
Nie wiem dokładnie, co się dzieje, ale wydaje się, że priorytet drugiego wątku jest tak wysoki, że w rzeczywistości uniemożliwia zaplanowanie wątku GUI. Jeśli drugi wątek przechodzi w stan ZABLOKOWANY (jak to ma miejsce w przypadkusleep()
) GUI ma szansę uruchomić i zaktualizować interfejs zgodnie z oczekiwaniami. Próbowałem zmienić priorytet wątku roboczego, ale wygląda na to, że nie można tego zrobić w systemie Linux.
Próbuję również wydrukować identyfikatory wątków, ale nie jestem pewien, czy robię to poprawnie. Jeśli jestem, powinowactwo nici wydaje się być poprawne.
Wypróbowałem także program za pomocą PyQt, a zachowanie jest dokładnie takie samo, stąd tagi i tytuł. Jeśli mogę sprawić, by działał z PyQt4 zamiast PySide, mógłbym przełączyć całą moją aplikację na PyQt4