Lỗi msgbox khi phân luồng, GUI chặn [đóng]

Nov 10 2020

Tôi gặp sự cố khi thực hiện lệnh sau. nó hoạt động bình thường nếu không có hộp thư, nhưng khi có hộp thư đến, nó sẽ chặn. bất kỳ ý tưởng tại sao gui chặn khi có tin nhắn. cảm ơn bạ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_())

Trả lời

1 musicamante Nov 10 2020 at 21:37

Mọi quyền truy cập vào các phần tử giao diện người dùng chỉ được phép từ bên trong luồng Qt chính. Lưu ý rằng quyền truy cập không chỉ có nghĩa là đọc / ghi các thuộc tính widget mà còn có nghĩa là tạo; bất kỳ nỗ lực nào để làm như vậy từ các chuỗi khác đều dẫn đến các sự cố đồ họa hoặc hành vi không nhất quán trong trường hợp tốt nhất và sự cố trong trường hợp xấu nhất (và phổ biến hơn).

Cách chính xác duy nhất để làm như vậy là sử dụng QThread với (có thể) các tín hiệu tùy chỉnh: điều này cho phép Qt xếp hàng chính xác các tín hiệu và phản ứng với chúng khi nó thực sự có thể xử lý chúng.

Sau đây là một tình huống rất đơn giản không yêu cầu tạo lớp con QThread, nhưng hãy cân nhắc rằng điều này chỉ dành cho mục đích giáo dục .

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

Vui lòng xem xét các khía cạnh quan trọng sau:

  • Tôi phải tắt nút bấm, nếu không có thể tạo một luồng mới trong khi luồng trước đó vẫn đang thực thi; điều này sẽ tạo ra một vấn đề, vì việc ghi đè self.threadsẽ khiến python cố gắng thu gom rác ( xóa ) luồng trước đó trong khi chạy, đây là một điều rất tệ ;
  • một giải pháp khả thi cho điều này là tạo luồng với cha mẹ, điều này thường được thực hiện đơn giản QThread(self), nhưng điều đó không khả thi trong trường hợp của bạn vì đối tượng Qt chỉ có thể chấp nhận các đối tượng Qt khác làm cha mẹ của chúng, trong khi trong trường hợp của bạn selfsẽ là một Ui_MainWindowví dụ (là một đối tượng python cơ bản);
  • điểm trên là một vấn đề quan trọng, bởi vì bạn đang cố gắng triển khai chương trình của mình bắt đầu từ một pyuictệp đã tạo, điều này không bao giờ nên thực hiện: những tệp đó được giữ nguyên như cũ mà không có bất kỳ sửa đổi thủ công nào và chỉ được sử dụng như các mô-đun đã nhập ; đọc thêm về chủ đề này trên các hướng dẫn chính thức về việc sử dụng Designer ; cũng lưu ý rằng cố gắng bắt chước hành vi của các tệp đó là vô ích, vì thường dẫn đến sự nhầm lẫn lớn về cấu trúc đối tượng;
  • về mặt lý thuyết, bạn có thể thêm một tham chiếu đến một đối tượng qt (ví dụ: bằng cách thêm self.mainWindow = MainWindowvào setupUi()hàm) và tạo chuỗi với tham chiếu đó ( thread = QThread(self.mainWindow)) hoặc thêm chuỗi vào danh sách liên tục ( self.threads = [], một lần nữa trong setupUi()), nhưng do những điều trên điểm tôi thực sự không khuyến khích bạn làm như vậy;

Cuối cùng, việc triển khai mã của bạn chính xác hơn sẽ yêu cầu bạn tạo lại tệp ui, giữ nguyên nó và thực hiện một số việc như ví dụ sau; lưu ý rằng tôi đã thêm một triển khai ngoại lệ rất cơ bản cũng chỉ ra cách tương tác chính xác với các tín hiệu tùy chỉnh.

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

Trong trường hợp trên, tệp ui được xử lý bằng lệnh sau (giả sử rằng ui được đặt tên là "mainwindow.ui", rõ ràng):

pyuic mainwindow.ui -o mainwindow.py