python多线程程序
在主程序用pyqt5建立界面,其中有一个TextBrowser。
然后在其中一线程内部更新TextBrowser内容。但用append可以,用clear或者set,程序就崩溃。
不知道为什么,求解答。
这个问题的原因在于 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_())
您好,我是有问必答小助手,您的问题已经有小伙伴帮您解答,感谢您对有问必答的支持与关注!