msgbox error saat threading, GUI diblokir [ditutup]
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
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 Andaselfakan menjadi sebuahUi_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 = MainWindowdalamsetupUi()fungsi) dan membuat thread dengan referensi yang (thread = QThread(self.mainWindow)), atau menambahkan benang ke daftar persisten (self.threads = [], lagi disetupUi()), 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