Próbuję wykonać dość pospolitą rzecz w mojej aplikacji PySide GUI: Chcę przekazać trochę zadania CPU-Intensive do wątku w tle, aby mój GUI pozostał reaguje, a nawet może wyświetlać wskaźnik postępu w obliczeniach.PySide/PyQt - Uruchamianie wątku intensywnie obciążającego procesor powoduje zawieszenie się całej aplikacji
Oto, co robię (używam PySide 1.1.1 na 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()
Aplikacja wyświetla okno z jednego przycisku. Po naciśnięciu przycisku oczekuję, że sam się wyłączy podczas wykonywania obliczeń. Następnie przycisk powinien zostać ponownie włączony.
To, co się dzieje, polega na tym, że po naciśnięciu przycisku całe okno zawiesza się, gdy obliczenia są wykonywane, a po zakończeniu odzyskuję kontrolę nad aplikacją. Przycisk nigdy nie wydaje się być wyłączony. Zabawną rzeczą, którą zauważyłem jest to, że jeśli zastąpię intensywne obliczenia procesora w do_stuff()
zwykłym time.sleep(), program zachowuje się zgodnie z oczekiwaniami.
Nie wiem dokładnie, co się dzieje, ale wygląda na to, że priorytet drugiego wątku jest tak wysoki, że faktycznie uniemożliwia zaplanowanie wątku GUI. Jeśli drugi wątek przechodzi w stan ZABLOKOWANY (tak jak w przypadku sleep()
), GUI ma faktycznie szansę uruchomienia i aktualizacji interfejsu 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 tak, powinowactwo wątku wydaje się poprawne.
Próbowałem również programu z PyQt i zachowanie jest dokładnie takie samo, stąd tagi i tytuł. Jeśli mogę uruchomić go z PyQt4 zamiast PySide, mogę zamienić całą aplikację na PyQt4
Próbowałem umieścić 'time.sleep (0)' zamiast komentarza 'time.sleep (0.2)' w przykładzie i, niestety, nie rozwiązało problemu. Zauważyłem, że z wartościami takimi jak 'time.sleep (0.05)' przycisk zachowuje się zgodnie z oczekiwaniami. Aby obejść ten problem, mógłbym skonfigurować pracownika do zgłaszania postępu tylko kilka razy na sekundę i przespać się, aby zaktualizować GUI. – user1491306
Istnieją obejścia dla wątków i GUI (takich jak http://code.activestate.com/recipes/578154). –
Skończyłem w ten sposób: wstawiłem "spać", gdzie zaczyna się obliczanie, i za każdym razem, gdy wskaźnik postępu jest aktualizowany, wołam 'app.processEvents()', aby zadziałał przycisk "Przerwij". – user1491306