Errore msgbox durante il threading, blocchi della GUI [chiuso]

Nov 10 2020

Ho un problema durante l'esecuzione della seguente interfaccia grafica. funziona normalmente se non c'è msgbox, ma quando c'è un mesbox si blocca. qualsiasi idea del perché la GUi si blocchi quando c'è un messaggio. grazie

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_())

Risposte

1 musicamante Nov 10 2020 at 21:37

Qualsiasi accesso agli elementi dell'interfaccia utente è consentito solo dall'interno del thread Qt principale. Notare che l' accesso significa non solo leggere / scrivere proprietà del widget, ma anche creazione; qualsiasi tentativo di farlo da altri thread si traduce in problemi grafici o comportamento incoerente nel migliore dei casi e un arresto anomalo nel caso peggiore (e più comune).

L'unico modo corretto per farlo è usare un QThread con (possibilmente) segnali personalizzati: questo consente a Qt di accodare correttamente i segnali e reagire ad essi quando può effettivamente elaborarli.

Quella che segue è una situazione molto semplice che non richiede la creazione di una sottoclasse QThread, ma considera che questa è solo per scopi educativi .

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()

Si prega di considerare i seguenti aspetti importanti:

  • Ho dovuto disabilitare il pulsante, altrimenti sarebbe stato possibile creare un nuovo thread mentre il precedente era ancora in esecuzione; questo creerà un problema, poiché la sovrascrittura self.threadfarà sì che Python provi a raccogliere in modo inutile ( eliminare ) il thread precedente durante l'esecuzione, il che è una cosa molto negativa ;
  • una possibile soluzione a questo è creare il thread con un genitore, che di solito viene fatto con un semplice QThread(self), ma non è possibile nel tuo caso perché gli oggetti Qt possono accettare solo altri oggetti Qt come loro genitore, mentre nel tuo caso selfsarebbe Ui_MainWindowun'istanza (che è un oggetto Python di base);
  • il punto precedente è una questione importante, perché stai cercando di implementare il tuo programma partendo da un pyuicfile generato, cosa che non dovrebbe mai essere fatta: quei file sono pensati per essere lasciati come sono senza alcuna modifica manuale, e usati solo come moduli importati ; leggi di più su questo argomento nelle linee guida ufficiali sull'uso di Designer ; si noti inoltre che cercare di imitare il comportamento di quei file è inutile, poiché normalmente porta a una grande confusione sulla struttura degli oggetti;
  • si potrebbe teoricamente aggiungere un riferimento a un oggetto qt (ad esempio, aggiungendo self.mainWindow = MainWindowla setupUi()funzione) e creare il thread con quel riferimento ( thread = QThread(self.mainWindow)), o aggiungere il thread a un elenco persistente ( self.threads = [], di nuovo in setupUi()), ma a causa di quanto sopra punto ti sconsiglio vivamente di farlo;

Infine, un'implementazione più corretta del tuo codice richiederebbe di generare nuovamente il file ui, lasciarlo così com'è e fare qualcosa di simile all'esempio seguente; nota che ho aggiunto un'implementazione di eccezioni molto semplice che mostra anche come interagire correttamente con i segnali personalizzati.

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_())

Nel caso precedente, il file ui è stato elaborato utilizzando il seguente comando (supponendo che l'interfaccia utente sia denominata "mainwindow.ui", ovviamente):

pyuic mainwindow.ui -o mainwindow.py