msgbox error saat threading, GUI diblokir [ditutup]

Nov 10 2020

Saya mengalami masalah saat menjalankan gui berikut. ini bekerja secara normal jika tidak ada kotak pesan, tetapi ketika ada kotak pesan itu diblokir. tahu mengapa gui memblokir saat ada pesan. Terima kasih

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

Jawaban

1 musicamante Nov 10 2020 at 21:37

Setiap akses ke elemen UI hanya diperbolehkan dari dalam utas Qt utama. Perhatikan bahwa akses tidak hanya berarti membaca / menulis properti widget, tetapi juga membuat; segala upaya untuk melakukannya dari utas lain menghasilkan masalah grafis atau perilaku yang tidak konsisten dalam kasus terbaik, dan kerusakan pada kasus terburuk (dan lebih umum).

Satu-satunya cara yang benar untuk melakukannya adalah dengan menggunakan QThread dengan (kemungkinan) sinyal khusus: ini memungkinkan Qt untuk mengantrekan sinyal dengan benar dan bereaksi ketika ia benar-benar dapat memprosesnya.

Berikut ini adalah situasi yang sangat sederhana yang tidak memerlukan pembuatan subkelas QThread, tetapi pertimbangkan bahwa ini hanya untuk tujuan pendidikan .

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

Harap pertimbangkan aspek penting berikut:

  • Saya harus menonaktifkan tombol tekan, jika tidak maka akan memungkinkan untuk membuat utas baru sementara yang sebelumnya masih dijalankan; ini akan menimbulkan masalah, karena penimpaan self.threadakan menyebabkan python mencoba mengumpulkan sampah ( menghapus ) utas sebelumnya saat berjalan, yang merupakan hal yang sangat buruk ;
  • solusi yang mungkin untuk ini adalah membuat utas dengan induk, yang biasanya dilakukan dengan sederhana QThread(self), tetapi itu tidak mungkin dalam kasus Anda karena objek Qt hanya dapat menerima objek Qt lain sebagai induknya, sedangkan dalam kasus Anda selfakan menjadi sebuah Ui_MainWindowinstance (yang merupakan objek python dasar);
  • poin di atas adalah masalah penting, karena Anda mencoba mengimplementasikan program Anda mulai dari pyuicfile yang dihasilkan, yang seharusnya tidak pernah dilakukan: file-file itu dimaksudkan untuk dibiarkan apa adanya tanpa modifikasi manual, dan hanya digunakan sebagai modul yang diimpor ; baca lebih lanjut tentang topik ini di pedoman resmi tentang menggunakan Designer ; juga perhatikan bahwa mencoba meniru perilaku file tersebut tidak berguna, karena biasanya menyebabkan kebingungan besar tentang struktur objek;
  • Anda secara teoritis bisa menambahkan referensi ke objek qt (misalnya, dengan menambahkan self.mainWindow = MainWindowdalam setupUi()fungsi) dan membuat thread dengan referensi yang ( thread = QThread(self.mainWindow)), atau menambahkan benang ke daftar persisten ( self.threads = [], lagi di setupUi()), namun karena di atas poin saya sangat tidak menyarankan Anda untuk melakukannya;

Terakhir, implementasi yang lebih tepat dari kode Anda akan mengharuskan Anda untuk menghasilkan lagi file ui, biarkan seperti itu dan lakukan sesuatu seperti contoh berikut; perhatikan bahwa saya menambahkan implementasi pengecualian yang sangat mendasar yang juga menunjukkan cara berinteraksi dengan benar dengan sinyal khusus.

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

Dalam kasus di atas, file ui diproses menggunakan perintah berikut (dengan asumsi bahwa ui bernama "mainwindow.ui", tentunya):

pyuic mainwindow.ui -o mainwindow.py