Ich versuche, eine ziemlich häufige Sache in meiner PySide GUI-Anwendung zu tun: Ich möchte einige CPU-intensive Aufgabe an einen Hintergrund Thread delegieren, so dass meine GUI bleibt reagieren und könnte sogar eine Fortschrittsanzeige anzeigen, wie die Berechnung geht.PySide/PyQt - Starten eines CPU-intensiven Threads hängt die ganze Anwendung
Hier ist, was ich tue (ich benutze pyside 1.1.1 auf 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()
Die Anwendung zeigt ein einzelnes Fenster mit einem Button. Wenn die Taste gedrückt wird, erwarte ich, dass sie sich selbst deaktiviert, während die Berechnung durchgeführt wird. Dann sollte die Schaltfläche wieder aktiviert werden.
Was passiert, ist stattdessen, dass, wenn ich den Knopf drücke das ganze Fenster einfriert, während die Berechnung geht und dann, wenn es fertig ist, ich wieder die Kontrolle über die Anwendung. Die Schaltfläche scheint nie deaktiviert zu sein. Eine lustige Sache, die ich bemerkte, ist, dass, wenn ich die CPU-intensive Berechnung in do_stuff()
mit einem einfachen time.sleep() ersetze, das Programm sich wie erwartet verhält.
Ich weiß nicht genau, was los ist, aber es scheint, dass die Priorität des zweiten Threads so hoch ist, dass es tatsächlich verhindert, dass der GUI-Thread überhaupt geplant wird. Wenn der zweite Thread in den BLOCKED-Zustand wechselt (wie es bei einem sleep()
geschieht), hat die GUI tatsächlich die Möglichkeit zu laufen und aktualisiert die Schnittstelle wie erwartet. Ich habe versucht, die Worker Thread-Priorität zu ändern, aber es sieht so aus, als ob es unter Linux nicht möglich ist.
Auch ich versuche, die Thread-IDs zu drucken, aber ich bin mir nicht sicher, ob ich es richtig mache. Wenn ich bin, scheint die Threadaffinität korrekt zu sein.
Ich habe auch versucht das Programm mit PyQt und das Verhalten ist genau das gleiche, daher die Tags und Titel. Wenn ich es mit PyQt4 statt PySide laufen lassen kann, könnte ich meine ganze Anwendung zu PyQt4 wechseln
Ich habe versucht, 'time.sleep (0)' anstatt des 'time.sleep (0.2)' Kommentar im Beispiel zu setzen und, leider, behebt das Problem nicht. Ich habe festgestellt, dass sich die Schaltfläche bei Werten wie "time.sleep (0.05)" wie erwartet verhält. Als Workaround konnte ich den Worker so einrichten, dass der Fortschritt nur einige Male pro Sekunde gemeldet wird, und der Ruhezustand, damit sich die GUI selbst aktualisieren kann. – user1491306
Es gibt Problemumgehungen für Threads und GUIs (z. B. http://code.activestate.com/recipes/578154). –
Ich endete so: Ich legte einen "Schlaf", wo die Berechnung beginnt, und jedes Mal, wenn die Fortschrittsanzeige aktualisiert wird, rufe ich 'app.processEvents()' auf, um einen "Abbrechen" -Button zu aktivieren. – user1491306