You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt6多线程更新UI报错:QObject::setParent跨线程问题

问题原因

你遇到的错误是因为PyQt的UI组件只能由创建它们的主线程操作。你的listen_for_messages_from_server函数运行在子线程中,直接调用add_message创建QLabel并添加到布局,属于跨线程操作UI,违反了Qt的线程规则,因此触发QObject::setParent: Cannot set parent, new parent is in a different thread错误。

解决方案:使用Qt信号与槽机制

Qt的信号与槽是线程安全的,子线程可以通过发送信号通知主线程更新UI,主线程的槽函数负责实际的UI操作。

修改步骤

  1. 在Window类中定义一个字符串类型的信号,用于传递收到的消息。
  2. 在__init__方法中将信号与add_message槽函数绑定。
  3. 在子线程的listen_for_messages_from_server函数中,不再直接调用add_message,而是发送信号。

修改后的完整代码

from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
from PyQt6.QtCore import *
import socket
import threading


class Window(QMainWindow):
    # 定义信号,用于传递收到的消息
    message_received = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.resize(600,600)
        self.show()

        # 将信号与add_message槽绑定
        self.message_received.connect(self.add_message)

        #central widget
        centWidget = QWidget()
        self.centWidgetLayout = QVBoxLayout()
        self.centWidgetLayout.setSpacing(0)
        self.centWidgetLayout.setContentsMargins(0,0,0,0)
        centWidget.setLayout(self.centWidgetLayout)
        self.setCentralWidget(centWidget)


        #join
        self.join = QPushButton('join')
        self.join.clicked.connect(self.connect)
        self.centWidgetLayout.addWidget(self.join)
 

        #message box
        self.contentWidget = QWidget()
        self.contentWidgetLayout = QVBoxLayout()
        self.contentWidget.setLayout(self.contentWidgetLayout)
        self.centWidgetLayout.addWidget(self.contentWidget)
        self.contentWidget.setFixedHeight(330)
        
        
        #send message
        self.button = QPushButton('send')
        self.centWidgetLayout.addWidget(self.button)
        self.button.clicked.connect(self.send_message)


    def add_message(self,message):
        print(message)
        self.msg = QLabel(message)
        self.contentWidgetLayout.addWidget(self.msg)
        


    def connect(self):
        self.HOST = '127.0.0.1'
        self.PORT = 1234
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
        try:
            self.client.connect((self.HOST, self.PORT))
            self.add_message("[SERVER] Successfully connected to the server")
        except:
            print('Error')

        username = 'john'
        if username != '':
            self.client.sendall(username.encode())
        else:
            print('Error')

        threading.Thread(target=self.listen_for_messages_from_server, args=(self.client, )).start()


    def send_message(self):
        message = 'message'
        if message != '':
            self.client.sendall(message.encode())
        else:
            print('error')


    def listen_for_messages_from_server(self,client):
        while True:
            message = client.recv(2048).decode('utf-8')
            if message != '':
                # 发送信号,由主线程处理UI更新
                self.message_received.emit(message)
            else:
                print('Message recevied from client is empty')

app = QApplication([])
win = Window()
app.exec()
另一种解决方案:使用QMetaObject.invokeMethod

如果不想用信号,也可以通过QMetaObject.invokeMethod直接在主线程执行add_message方法,修改listen_for_messages_from_server中的代码即可:

def listen_for_messages_from_server(self,client):
    while True:
        message = client.recv(2048).decode('utf-8')
        if message != '':
            # 调用主线程的add_message方法
            QMetaObject.invokeMethod(
                self,
                "add_message",
                Qt.ConnectionType.QueuedConnection,
                Q_ARG(str, message)
            )
        else:
            print('Message recevied from client is empty')

这种方式不需要额外定义信号,直接指定在主线程执行目标方法,同样能避免跨线程UI操作的错误。

内容的提问来源于stack exchange,提问作者coder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 23:22:50