pyqt5 子執行緒如何操作主執行緒GUI

余生没有余生發表於2024-05-17

一.簡介

在使用pyqt5編寫gui時遇到兩個問題,會導致介面崩潰,今天就圍繞這兩個問題來簡單說明和改進。

1.在主執行緒中使用while無限迴圈會導致介面崩潰

2.在子執行緒中操作主執行緒gui會導致介面崩潰

二.步驟說明

1.在主執行緒中使用while無限迴圈會導致介面崩潰

1)錯誤程式碼

pyqt5 子執行緒如何操作主執行緒GUI
import sys

from PyQt5.QtWidgets import  QPushButton, QTextEdit, QApplication, QHBoxLayout, QWidget


class FileChooserApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        button = QPushButton("按鈕")

        self.reviewEdit = QTextEdit()
        self.reviewEdit.move(100, 100)

        button.clicked.connect(self.send)

        hbox1 = QHBoxLayout()  # 建立一個水平佈局
        hbox1.addWidget(button)  # 新增按鈕到水平佈局中
        hbox1.addStretch(1)  # 設定水平比例間距
        hbox1.addWidget(self.reviewEdit)  # 新增按鈕到水平佈局中


        self.setLayout(hbox1)  # 新增到佈局器
        self.setWindowTitle('檔案選擇器')
        self.setGeometry(300, 300, 500, 500)

    def send(self):
        """
        事件
        :return:
        """

        while True:
            """
            邏輯程式碼
            """
            self.reviewEdit.setText("測試")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = FileChooserApp()
    ex.show()
    sys.exit(app.exec_())
View Code

2)崩潰原因

我們先來說下while崩潰的問題,這邊我設定的迴圈是一個無限迴圈,不會給 GUI 事件迴圈任何執行的機會。在 PyQt 或其他 GUI 框架中,GUI 的事件迴圈(例如按鈕點選、視窗移動等)必須在單獨的執行緒中執行,以保持 GUI 的響應性

3)改進方法

將迴圈體在一個子執行緒中執行,就可以避免這個問題,程式碼如下。

pyqt5 子執行緒如何操作主執行緒GUI
import sys
import threading

from PyQt5.QtWidgets import QPushButton, QTextEdit, QApplication, QHBoxLayout, QWidget


class FileChooserApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        button = QPushButton("按鈕")

        self.reviewEdit = QTextEdit()
        self.reviewEdit.move(100, 100)

        button.clicked.connect(self.send)

        hbox1 = QHBoxLayout()  # 建立一個水平佈局
        hbox1.addWidget(button)  # 新增按鈕到水平佈局中
        hbox1.addStretch(1)  # 設定水平比例間距
        hbox1.addWidget(self.reviewEdit)  # 新增按鈕到水平佈局中

        self.setLayout(hbox1)  # 新增到佈局器
        self.setWindowTitle('檔案選擇器')
        self.setGeometry(300, 300, 500, 500)

    def send(self):
        """
        事件
        :return:
        """

        def send_a():
            while True:
                """
                邏輯程式碼
                """
                print("123")

        send_thread = threading.Thread(target=send_a)
        # 啟動執行緒
        send_thread.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = FileChooserApp()
    ex.show()
    sys.exit(app.exec_())
View Code

2.在子執行緒中操作主執行緒gui會導致介面崩潰

1)錯誤程式碼

pyqt5 子執行緒如何操作主執行緒GUI
import sys
import threading
import time

from PyQt5.QtWidgets import QPushButton, QTextEdit, QApplication, QHBoxLayout, QWidget


class FileChooserApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        button = QPushButton("按鈕")

        self.reviewEdit = QTextEdit()
        self.reviewEdit.move(100, 100)

        button.clicked.connect(self.send)

        hbox1 = QHBoxLayout()  # 建立一個水平佈局
        hbox1.addWidget(button)  # 新增按鈕到水平佈局中
        hbox1.addStretch(1)  # 設定水平比例間距
        hbox1.addWidget(self.reviewEdit)  # 新增按鈕到水平佈局中

        self.setLayout(hbox1)  # 新增到佈局器
        self.setWindowTitle('檔案選擇器')
        self.setGeometry(300, 300, 500, 500)

    def send(self):
        """
        事件
        :return:
        """

        def send_a():
            while True:
                """
                邏輯程式碼
                """
                self.reviewEdit.setText("設定文案")

        send_thread = threading.Thread(target=send_a)
        # 啟動執行緒
        send_thread.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = FileChooserApp()
    ex.show()
    sys.exit(app.exec_())
View Code

2)崩潰原因

上述中試圖在子執行緒send_a方法中給文字編輯器設定文案。這是不允許的,因為 PyQt 和大多數 GUI 框架一樣,要求所有的 GUI 更新必須在主執行緒(也稱為 GUI 執行緒)中執行。

3)改進方法

既然在子執行緒中無法操作主執行緒gui更新,那麼只能在主執行緒中去執行,這就需要訊號與槽的配合了。我們先來自定義一個訊號

class YourThread(QObject):
    show_warning_signal = pyqtSignal()

    def run(self):
        self.show_warning_signal.emit()

在初始化的時候去例項化YourThread類,連線訊號與槽

class FileChooserApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.your = YourThread()
        self.your.show_warning_signal.connect(self.settext)

接著在子執行緒中直接去觸發訊號即可

    def send(self):
        def send_a():
            while True:
                """
                迴圈體
                """
                self.your.run()
                time.sleep(2)

        send_thread = threading.Thread(target=send_a)
        # 啟動執行緒
        send_thread.start()

執行每次迴圈需要等待2s,避免出現無限迴圈導致應用程式凍結、響應緩慢或其他執行緒相關的問題

三.例項

import sys
import threading
import time

from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QPushButton, QTextEdit, QApplication, QHBoxLayout, QWidget


class YourThread(QObject):
    show_warning_signal = pyqtSignal()

    def run(self):
        self.show_warning_signal.emit()


class FileChooserApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.your = YourThread()
        self.your.show_warning_signal.connect(self.settext)

    def initUI(self):
        button = QPushButton("按鈕")

        self.reviewEdit = QTextEdit()
        self.reviewEdit.move(100, 100)

        button.clicked.connect(self.send)

        hbox1 = QHBoxLayout()  # 建立一個水平佈局
        hbox1.addWidget(button)  # 新增按鈕到水平佈局中
        hbox1.addStretch(1)  # 設定水平比例間距
        hbox1.addWidget(self.reviewEdit)  # 新增按鈕到水平佈局中

        self.setLayout(hbox1)  # 新增到佈局器
        self.setWindowTitle('檔案選擇器')
        self.setGeometry(300, 300, 500, 500)

    def send(self):
        """
        事件
        :return:
        """

        def send_a():
            while True:
                """
                邏輯程式碼
                """
                self.your.run()
                time.sleep(2)

        send_thread = threading.Thread(target=send_a)
        # 啟動執行緒
        send_thread.start()

    def settext(self):
        self.reviewEdit.setText("123")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = FileChooserApp()
    ex.show()
    sys.exit(app.exec_())

相關文章