多线程TextBrowser无法用clear,set。

python多线程程序
在主程序用pyqt5建立界面,其中有一个TextBrowser。
然后在其中一线程内部更新TextBrowser内容。但用append可以,用clear或者set,程序就崩溃。
不知道为什么,求解答。

TechWhizKid参考GPT回答:

  • 这个问题的原因在于 PyQt5 的 GUI 更新必须在主线程中进行,而不能在子线程中进行。在子线程中直接更新 GUI 会导致程序崩溃。这是因为 GUI 组件的绘制和事件处理都是在主线程中完成的,如果在子线程中更新 GUI,可能会导致 GUI 组件的状态在绘制过程中发生改变,从而引发各种问题。

  • 你可以通过信号和槽机制来解决这个问题。在子线程中,当需要更新 GUI 时,发出一个信号;在主线程中,接收这个信号,并在槽函数中更新 GUI。这样就可以避免在子线程中直接操作 GUI。

以下是一个简单的例子:

from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QTextBrowser

class Worker(QThread):
    textChanged = pyqtSignal(str)

    def run(self):
        for i in range(100):
            self.textChanged.emit(str(i))
            QThread.sleep(1)

class Window(QTextBrowser):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
        self.worker = Worker()
        self.worker.textChanged.connect(self.append)
        self.worker.start()

app = QApplication([])
window = Window()
window.show()
app.exec_()

Worker 是一个子线程,它有一个 textChanged 信号。当需要更新 GUI 时,它发出 textChanged 信号,而不是直接操作 GUI。Window 是主线程中的 GUI 组件,它接收 textChanged 信号,并在槽函数 append 中更新 GUI。

根本原因在于:QT GUI不支持在多个线程共用(或者是线程不安全的),在子线程中处理TextBrowser控件会导致UI资源管理混乱。这就是为什么有的函数能起作用,有的函数不起作用的原因。(很多时候,哪些函数起作用、哪些不起作用是不确定的)
正确的方法是:在建立线程的时候,在界面和线程之间建立信号槽关系。在线程中处理到你的逻辑后,线程向界面发送信号,界面收到信号后去更新界面内容。

首先,请确保您正在正确地使用PyQt5的线程安全方法来访问GUI元素。这意味着您需要使用QMetaObject.invokeMethod()方法来确保所有对GUI元素的访问都发生在GUI线程中。具体而言,您可以使用以下代码将数据从子线程发送到TextBrowser:

from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot

class Signal(QObject):
    update_text = pyqtSignal(str)

class MyThread(QtCore.QThread):

    def __init__(self, parent=None):
        super(MyThread, self).__init__(parent)
        self.signal = Signal()

    def run(self):
        # 在此处执行您希望在子线程中完成的任务
        self.signal.update_text.emit("Hello world!")  # 发送信号

class MainWidget(QWidget):

    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)
        self.text_browser = QTextBrowser()
        layout = QVBoxLayout()
        layout.addWidget(self.text_browser)
        self.setLayout(layout)

        self.thread = MyThread()
        self.thread.signal.update_text.connect(self.update_text_browser)  # 接受信号
        self.thread.start()

    @pyqtSlot(str)
    def update_text_browser(self, text):
        self.text_browser.append(text)

在上面的代码中,我们创建了一个名为MyThread的子线程类,并在其中使用了一个名为Signal的自定义QObject来定义了一个update_text信号。然后,我们可以在该线程中执行所需任务,并在完成该任务时通过调用self.signal.update_text.emit(text)来发出update_text信号,该信号将包含更新TextBrowser所需的文本数据。

在主窗口小部件中,我们创建了一个名为MainWidget的QWidget,并添加了一个QTextBrowser。然后,我们创建了一个MyThread实例并启动它。接着,我们使用thread.signal.update_text.connect(self.update_text_browser)语句连接了update_text信号和名为update_text_browser的槽函数。每当子线程发出update_text信号时,都会调用该槽函数,并将更新TextBrowser所需的文本数据传递给它。

可能是多线程造成了TextBrowser的异常。在多线程中更新TextBrowser内容时,建议使用pyqtSignal来实现线程间通信。具体来说,可以在主线程中定义一个pyqtSignal信号,当子线程需要更新TextBrowser内容时,通过该信号调用主线程中的槽函数,在槽函数中更新TextBrowser的内容。

以下是一个简单的示例代码:


python
from PyQt5 import QtWidgets, QtCore
import threading
import time

class Example(QtWidgets.QWidget):
    update_signal = QtCore.pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.init_ui()
        self.thread = MyThread()

    def init_ui(self):
        self.text_browser = QtWidgets.QTextBrowser(self)
        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(self.text_browser)
        self.setLayout(layout)
        self.update_signal.connect(self.update_text_browser)

    def start_thread(self):
        self.thread.start()

    def update_text_browser(self, text):
        cursor = self.text_browser.textCursor()
        cursor.movePosition(QtGui.QTextCursor.End)
        cursor.insertText(text + '\n')

    def clear_text_browser(self):
        self.text_browser.clear()

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()

    def run(self):
        for i in range(10):
            time.sleep(1)
            # 发送信号,更新TextBrowser的内容
            example.update_signal.emit(f'Thread output {i}')

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    example = Example()
    example.show()
    example.start_thread()
    app.exec_()


在这个示例中,当子线程需要更新TextBrowser的内容时,通过emit方法发送信号,然后在主线程中的槽函数update_text_browser中更新TextBrowser的内容。这样就避免了多线程造成的异常问题。

可能是因为在多线程程序中,GUI界面更新需要在主线程中进行,而不是在其他线程中进行。因此,建议使用Qt的信号与槽机制,在其他线程中发射信号,然后在主线程中连接槽函数来更新TextBrowser的内容。例如:

from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot

class Worker(QObject):
    updateText = pyqtSignal(str)

    def run(self):
        # 在其他线程中更新TextBrowser的内容
        text = "Hello, world!"
        self.updateText.emit(text)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # 创建TextBrowser
        self.textBrowser = QTextBrowser()
        self.setCentralWidget(self.textBrowser)

        # 创建Worker对象并启动线程
        self.worker = Worker()
        self.thread = QThread()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.thread.start()

        # 连接信号与槽函数
        self.worker.updateText.connect(self.updateTextBrowser)

    @pyqtSlot(str)
    def updateTextBrowser(self, text):
        # 在主线程中更新TextBrowser的内容
        self.textBrowser.append(text)

在上面的代码中,我们创建了一个Worker对象,在其中启动一个线程来更新TextBrowser的内容。在Worker中,我们定义了一个updateText信号,用于在其他线程中发射信号。在MainWindow中,我们创建了一个Worker对象,并将其移动到一个新的线程中。然后,我们连接Worker的updateText信号到MainWindow的updateTextBrowser槽函数,用于在主线程中更新TextBrowser的内容。当Worker在其他线程中更新TextBrowser的内容时,它会发射updateText信号,然后在主线程中调用updateTextBrowser槽函数来更新TextBrowser的内容。

建议使用Queue来将数据传递回主线程中,再在主线程中更新TextBrowser的内容,这个是案例代码

import sys
import threading
from queue import Queue
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextBrowser

class MainGUI(QMainWindow):
    def __init__(self):
        super().__init__()

        # 创建TextBrowser对象
        self.text_browser = QTextBrowser(self)
        self.text_browser.setGeometry(50, 50, 400, 300)

        # 创建Queue对象
        self.queue = Queue()

        # 创建子线程
        thread = threading.Thread(target=self.worker_thread, daemon=True)
        thread.start()

    def worker_thread(self):
        # 模拟子线程中不断更新TextBrowser的内容
        while True:
            # 从Queue中获取数据
            data = self.queue.get()

            # 在不同线程中只能使用线程所创建的QApplication对象的方法,否则可能会导致异常
            QApplication.instance().postEvent(self.text_browser, UpdateEvent(data))
            
    def update_text_browser(self, text):
        # 将数据添加到Queue中,等待主界面处理
        self.queue.put(text)
    
class UpdateEvent:
    def __init__(self, data):
        self.data = data
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    gui = MainGUI()
    gui.show()
    sys.exit(app.exec_())

您好,我是有问必答小助手,您的问题已经有小伙伴帮您解答,感谢您对有问必答的支持与关注!
PS:问答VIP年卡 【限时加赠:IT技术图书免费领】,了解详情>>> https://vip.csdn.net/askvip?utm_source=1146287632