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

Socket聊天App开发遇AttributeError:'App'对象无'gui_done'属性

Socket聊天应用线程同步问题

我在开发Socket聊天应用时,遇到GUI线程与Socket监听线程的同步问题,导致运行时触发属性错误。

代码实现

import socket
import threading
from tkinter import Tk, Label, ScrolledText, Entry, Button, END

class App():

    def __init__(self, user):
        self.user=user
        self.clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.clientSocket.connect(("127.0.0.1", 9090))
        guit=threading.Thread(target=self.gui)
        lstn=threading.Thread(target=self.listen)
        
        guit.start()
        lstn.start()
        
    def gui(self):  
        self.root=Tk()
        self.username=Label(self.root, font=("Arial", 15), text=self.user)
        self.username.grid(column=0, row=0)
        self.hist=ScrolledText(self.root, width=40, height=25 , font=("Arial",15))
        self.hist.config(state=DISABLED)
        self.hist.grid(column=0,row=1, columnspan=2)
        self.msg=Entry(self.root, font=("Arial",15))
        self.msg.grid(row=2,column=0)
        btnSend=Button(self.root, font=("Arial",15), text="Send", command=self.send)
        btnSend.grid(row=2, column=1)
        self.gui_done=True
        self.root.bind("<Return>", self.send)
        self.root.mainloop()
        
    
    def addMessage(self, message):
        self.hist.configure(state=NORMAL)
        self.hist.insert(END, f'{message}\n')
        self.hist.configure(state=DISABLED)
        
    
    def send(self,e=None):
        self.clientSocket.send(f'[{self.user}]{self.msg.get()}'.encode())
        print(self.msg.get())
        self.msg.delete(0,END)
    
    def listen(self):
        self.clientSocket.send(f'[SERVER]{self.user} has joined'.encode())
        while 1:
            msgFromServer=self.clientSocket.recv(1024)
            print(msgFromServer)
            if self.gui_done:
                self.addMessage(msgFromServer.decode())

报错信息

Exception in thread Thread-2 (listen):
Traceback (most recent call last):
File "C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.10_3.10.2288.0_x64__qbz5n2kfra8p0\\lib\\threading.py", line 1016, in _bootstrap_inner
self.run()
File "C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.10_3.10.2288.0_x64__qbz5n2kfra8p0\\lib\\threading.py", line 953, in run
self._target(*self._args, **self._kwargs)
File "C:\\code\\https\\qttest\\client1.py", line 52, in listen
if self.gui_done:
AttributeError: 'App' object has no attribute 'gui_done'

问题根源

listen线程与gui线程几乎同时启动,listen线程可能在gui线程完成初始化并设置self.gui_done属性前,就尝试访问该属性,触发AttributeError。

解决方案

方案1:提前初始化属性

在类的构造函数中提前定义self.gui_done并设为False,避免属性未定义的错误:

def __init__(self, user):
    self.user=user
    self.gui_done = False  # 提前初始化属性
    self.clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.clientSocket.connect(("127.0.0.1", 9090))
    guit=threading.Thread(target=self.gui)
    lstn=threading.Thread(target=self.listen)
    
    guit.start()
    lstn.start()

方案2:使用线程Event实现可靠等待

用threading.Event替代布尔变量,让listen线程等待GUI初始化完成后再处理消息,更安全可靠:

import threading

class App():
    def __init__(self, user):
        self.user=user
        self.gui_ready = threading.Event()  # 创建同步事件
        self.clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.clientSocket.connect(("127.0.0.1", 9090))
        guit=threading.Thread(target=self.gui)
        lstn=threading.Thread(target=self.listen)
        
        guit.start()
        lstn.start()
        
    def gui(self):  
        self.root=Tk()
        # 保留原GUI初始化代码
        self.username=Label(self.root, font=("Arial", 15), text=self.user)
        self.username.grid(column=0, row=0)
        self.hist=ScrolledText(self.root, width=40, height=25 , font=("Arial",15))
        self.hist.config(state=DISABLED)
        self.hist.grid(column=0,row=1, columnspan=2)
        self.msg=Entry(self.root, font=("Arial",15))
        self.msg.grid(row=2,column=0)
        btnSend=Button(self.root, font=("Arial",15), text="Send", command=self.send)
        btnSend.grid(row=2, column=1)
        self.gui_ready.set()  # 标记GUI初始化完成
        self.root.bind("<Return>", self.send)
        self.root.mainloop()
    
    def listen(self):
        self.clientSocket.send(f'[SERVER]{self.user} has joined'.encode())
        self.gui_ready.wait()  # 等待GUI准备完成
        while 1:
            msgFromServer=self.clientSocket.recv(1024)
            print(msgFromServer)
            self.addMessage(msgFromServer.decode())

方案3:确保GUI操作在主线程执行

Tkinter要求所有GUI操作必须在创建它的线程中执行,若后续出现GUI卡顿或报错,可通过self.root.after()将addMessage调用切换到GUI线程:

# 在listen线程中修改调用方式
self.root.after(0, self.addMessage, msgFromServer.decode())

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 12:15:38