msgbox Fehler beim Threading, GUI blockiert [geschlossen]
Ich habe ein Problem beim Ausführen der folgenden GUI. es funktioniert normal, wenn es keine msgbox gibt, aber wenn es eine mesbox gibt, blockiert es. jede Idee, warum die GUI blockiert, wenn es eine Nachricht gibt. Dankeschön
from PyQt5 import QtCore, QtGui, QtWidgets
import threading
import time
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.verticalLayout.addWidget(self.pushButton)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.pushButton.pressed.connect(self.threadingc)
def calculation(self):
for i in range(10):
time.sleep(1)
print(i)
msg = QtWidgets.QMessageBox()
msg.setInformativeText('Finish')
msg.exec_()
def threadingc(self):
x=threading.Thread(target=self.calculation)
x.start()
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
import sys
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Antworten
Jeder Zugriff auf UI-Elemente ist nur innerhalb des Qt-Hauptthreads zulässig. Beachten Sie, dass Zugriff nicht nur das Lesen / Schreiben von Widget-Eigenschaften, sondern auch das Erstellen bedeutet. Jeder Versuch, dies von anderen Threads aus zu tun, führt im besten Fall zu grafischen Problemen oder inkonsistentem Verhalten und im schlimmsten (und häufigeren) Fall zu einem Absturz.
Der einzig richtige Weg, dies zu tun, ist die Verwendung eines QThreads mit (möglicherweise) benutzerdefinierten Signalen: Dadurch kann Qt Signale korrekt in die Warteschlange stellen und auf sie reagieren, wenn es sie tatsächlich verarbeiten kann.
Das Folgende ist eine sehr einfache Situation, in der keine QThread-Unterklasse erstellt werden muss. Beachten Sie jedoch, dass dies nur zu Bildungszwecken dient.
class Ui_MainWindow(object):
# ...
def calculation(self):
for i in range(10):
time.sleep(1)
print(i)
def showMessage(self):
msg = QtWidgets.QMessageBox()
msg.setInformativeText('Finish')
msg.exec_()
self.pushButton.setEnabled(True)
def threadingc(self):
self.pushButton.setEnabled(False)
self.thread = QtCore.QThread()
# override the `run` function with ours; this ensures that the function
# will be executed in the new thread
self.thread.run = self.calculation
self.thread.finished.connect(self.showMessage)
self.thread.start()
Bitte beachten Sie folgende wichtige Aspekte:
- Ich musste den Druckknopf deaktivieren, sonst wäre es möglich, einen neuen Thread zu erstellen, während der vorherige noch ausgeführt wird. Dies führt zu einem Problem, da das Überschreiben dazu
self.threadführt, dass Python versucht, den vorherigen Thread während der Ausführung im Müll zu sammeln (zu löschen ), was eine sehr schlechte Sache ist. - Eine mögliche Lösung hierfür besteht darin, den Thread mit einem übergeordneten Element zu erstellen. Dies erfolgt normalerweise mit einem einfachen Thread. Dies ist
QThread(self)in Ihrem Fall jedoch nicht möglich, da Qt-Objekte nur andere Qt-Objekte als übergeordnetes Element akzeptieren können, während dies in Ihrem FallselfeineUi_MainWindowInstanz wäre (das ist ein grundlegendes Python-Objekt); - Der obige Punkt ist ein wichtiges Thema, weil Sie versuchen , Ihr Programm zu implementieren von einer Start
pyuicgenerierte Datei, die sollte nie geschehen: wie sie ohne sind gelassen werden diese Dateien soll jede manuelle Änderung und nur als importierte Module ;; Weitere Informationen zu diesem Thema finden Sie in den offiziellen Richtlinien zur Verwendung von Designer . Beachten Sie auch, dass der Versuch, das Verhalten dieser Dateien nachzuahmen, nutzlos ist, da dies normalerweise zu großer Verwirrung über die Objektstruktur führt. - Sie könnten theoretisch einen Verweis auf ein qt-Objekt hinzufügen (z. B. durch Hinzufügen
self.mainWindow = MainWindowdersetupUi()Funktion) und den Thread mit diesem Verweis erstellen (thread = QThread(self.mainWindow)) oder den Thread zu einer persistenten Liste hinzufügen (ebenfallsself.threads = []in dersetupUi()), jedoch aus den oben genannten Gründen Ich rate Ihnen dringend davon ab.
Schließlich würde eine korrektere Implementierung Ihres Codes erfordern, dass Sie die UI-Datei erneut generieren, sie unverändert lassen und etwa das folgende Beispiel ausführen. Beachten Sie, dass ich eine sehr grundlegende Ausnahmeimplementierung hinzugefügt habe , die auch zeigt, wie mit benutzerdefinierten Signalen korrekt interagiert wird.
from PyQt5 import QtCore, QtGui, QtWidgets
from mainwindow import Ui_MainWindow
import time
class Calculation(QtCore.QThread):
error = QtCore.pyqtSignal(object)
def run(self):
for i in range(10):
time.sleep(1)
print(i)
try:
10 / 0
except Exception as e:
self.error.emit(e)
break
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.pushButton.pressed.connect(self.threadingc)
def showMessage(self):
msg = QtWidgets.QMessageBox()
msg.setInformativeText('Finish')
msg.exec_()
def threadingc(self):
# create the thread with the main window as a parent, this is possible
# since QMainWindow also inherits from QObject, and this also ensures
# that python will not delete it if you want to start another thread
thread = Calculation(self)
thread.finished.connect(self.showMessage)
thread.error.connect(self.showError)
thread.start()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
Im obigen Fall wurde die UI-Datei mit dem folgenden Befehl verarbeitet (vorausgesetzt, die UI heißt offensichtlich "mainwindow.ui"):
pyuic mainwindow.ui -o mainwindow.py