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

CustomTkinter界面因聊天接收线程卡顿的解决方案咨询

师生管理系统客户端GUI卡顿问题优化

我正在用Python开发师生管理系统作为毕业设计,客户端主函数运行两个线程:一个负责与服务器通信,另一个负责GUI。聊天功能激活时会启动额外线程接收消息,但启动该线程后CustomTkinter界面严重卡顿,移除线程后恢复正常,求优化方案。

主函数代码

if __name__=="__main__":
    client = socket.socket()
    hostname = socket.gethostname()
    ip_adress = socket.gethostbyname(hostname)
    while(True):
        try:
            client.connect((ip_adress, 1729))
            break
        except:
            messagebox.showerror('error', ' communication failed')


    msg_send = []
    msg_recieve=[]
    locker=threading.Lock()

    socket_thread=threading.Thread(target=client_socket, args=(client,msg_send,locker,msg_recieve))
    gui_thread=threading.Thread(target=lambda : login_gui(msg_send,msg_recieve))
    socket_thread.start()
    gui_thread.start()
    socket_thread.join()
    gui_thread.join()

聊天函数代码

def chat(root, frame, id, msg_send, msg_recieve):
    def send_message(recipient_id,content,messages_frame):
        def print_msg(content):
            f = customtkinter.CTkFrame(messages_frame, width=480, height=30)
            f.pack()
            customtkinter.CTkLabel(f, font=('Century Gothic', 15),fg_color="dark green", text=content,corner_radius=6,width=56).place(x=400)


        if content.strip():
            message = Message.Message(content, id)
            message.set_recipient_id(recipient_id)
            msg_send.append(message)
            print_msg(content)

    def receiving_messages(event,chat_id,messages_frame):
        def print_msg(content):
            f = customtkinter.CTkFrame(messages_frame, width=480, height=30)
            f.pack()
            customtkinter.CTkLabel(f, font=('Century Gothic', 15),fg_color='gray31', text=content,corner_radius=6).place(x=2)

        while True:
            i = 0
            while i < len(msg_recieve):
                if event.is_set():
                    break
                try:
                    if msg_recieve[i].get_class_code() == 1:
                        if msg_recieve[i].get_sender_id()==chat_id:
                            content = msg_recieve[i].get_text()
                            print_msg(content)
                        del msg_recieve[i]
                    else:
                        i += 1
                except Exception as e:
                    print("Error1:", e)
            if event.is_set():
                break

    def close_chat(root, frame, id, msg_send, msg_recieve, event):
        event.set()
        msg_send.append(f"disconnect:{id}")
        main(root, frame, id, msg_send, msg_recieve)

    def build_chat(data,chat_frame,event):

        clear_frame(chat_frame)
        msg_send.append(f"disconnect:{id}")
        messages_frame = customtkinter.CTkScrollableFrame(chat_frame, height=520, width=480,
                                                          label_text="Message "+data[0],
                                                          label_font=('Century Gothic', 25))
        messages_frame.place(y=0, x=0)

        msg_send.append(f"get:messages:{id}:{data[1]}")
        server_respond = False
        while not server_respond:
            i = 0
            while not server_respond and i < len(msg_recieve):
                try:
                    if msg_recieve[i].get_class_code() == 6:
                        list = msg_recieve[i].get_messages()
                        server_respond = True
                        del msg_recieve[i]
                    else:
                        i += 1
                except Exception as e:
                    print("Error:", e)
        for i in list:
            print(i.get_time())
            f = customtkinter.CTkFrame(messages_frame, width=480, height=30)
            f.pack()
            if i.get_sender_id()==id:
                    customtkinter.CTkLabel(f, font=('Century Gothic', 15), fg_color="dark green", text=i.get_text(),
                                           corner_radius=6, width=56).place(x=400)
            else:
                customtkinter.CTkLabel(f, font=('Century Gothic', 15), fg_color='gray31', text=i.get_text(),
                                       corner_radius=6).place(x=2)
        msg_send.append(f"connect:{id}")

        message_entry = customtkinter.CTkEntry(chat_frame, width=460, height=30, placeholder_text="message...",
                                               font=('Century Gothic', 20))
        message_entry.place(x=0, y=575)
        send_button = customtkinter.CTkButton(chat_frame, width=20, height=30, font=('Century Gothic', 20), corner_radius=6,
                                              text="➤",command=lambda :send_message(data[1],message_entry.get(),messages_frame))
        send_button.place(x=460, y=575)

        recieve_thread = threading.Thread(target=receiving_messages, args=(event,data[1],messages_frame))
       
        recieve_thread.start()

    event = threading.Event()
    clear_frame(frame)
    back_button = customtkinter.CTkButton(master=frame, width=120, text="back",
                                          font=('Century Gothic', 18), corner_radius=6, cursor='hand2',
                                          command=lambda: close_chat(root, frame, id, msg_send, msg_recieve, event))
    back_button.place(y=0, x=0)


    msg_send.append(f"get:students:{id}")
    server_respond = False
    while not server_respond:
        i = 0
        while not server_respond and i < len(msg_recieve):
            try:
                if msg_recieve[i].get_class_code() == 4:
                    list = msg_recieve[i].get_list()
                    server_respond = True
                    del msg_recieve[i]
                else:
                    i += 1
            except Exception as e:
                print("Error:", e)
    if list:
        chat_frame = customtkinter.CTkFrame(frame, width=500, height=605)
        chat_frame.place(y=0, x=300)
        students_frame = customtkinter.CTkScrollableFrame(frame, height=520, width=200)
        students_frame.place(y=50, x=20)
        for student in list:
            customtkinter.CTkButton(master=students_frame,fg_color="royal blue",width=190, text=student[0], font=('Century Gothic', 20),corner_radius=1,command=lambda data=(student[0],student[1]): build_chat(data,chat_frame,event)).pack(pady=2)

优化解决方案

1. 给接收线程添加休眠,减少CPU占用

当前receiving_messages线程无间隔轮询消息列表,会耗尽大量CPU资源,导致GUI线程被抢占。在循环内添加短暂休眠,平衡响应速度和资源占用:

import time
# 在receiving_messages的while True循环内添加
time.sleep(0.05)  # 每50ms检查一次消息

2. 强制GUI操作在主线程执行

CustomTkinter(及底层Tkinter)的GUI组件只能由创建它们的主线程操作,当前接收线程直接创建组件属于违规操作,会引发界面卡顿和异常。改用root.after()将GUI更新逻辑交给主线程处理:

def receiving_messages(event,chat_id,messages_frame):
    def print_msg(content):
        # 封装GUI操作,交给主线程执行
        def update_gui():
            f = customtkinter.CTkFrame(messages_frame, width=480, height=30)
            f.pack()
            customtkinter.CTkLabel(f, font=('Century Gothic', 15),fg_color='gray31', text=content,corner_radius=6).place(x=2)
        root.after(0, update_gui)  # 0表示立即在主线程执行

    while True:
        if event.is_set():
            break
        # 加锁保护共享列表,避免竞态条件
        with locker:
            i = 0
            while i < len(msg_recieve):
                try:
                    if msg_recieve[i].get_class_code() == 1 and msg_recieve[i].get_sender_id()==chat_id:
                        content = msg_recieve[i].get_text()
                        print_msg(content)
                    del msg_recieve[i]
                except Exception as e:
                    print("Error1:", e)
                    i += 1
        time.sleep(0.05)

3. 修复共享列表的线程安全问题

msg_send和msg_recieve是多线程共享资源,所有对这两个列表的读写操作都必须加锁,避免竞态条件引发的异常和卡顿:

# 发送消息时加锁
with locker:
    msg_send.append(message)

# socket线程读取msg_send、写入msg_recieve时也要加锁
with locker:
    if msg_send:
        # 处理发送消息逻辑
        pass

4. 用队列替代列表实现事件驱动

改用queue.Queue存储消息,队列本身是线程安全的,且get()方法会阻塞等待新消息,彻底避免轮询带来的资源浪费:

from queue import Queue
# 主函数里替换列表为队列
msg_send = Queue()
msg_recieve = Queue()

# 接收线程修改为事件驱动
def receiving_messages(event,chat_id,messages_frame):
    def print_msg(content):
        def update_gui():
            f = customtkinter.CTkFrame(messages_frame, width=480, height=30)
            f.pack()
            customtkinter.CTkLabel(f, font=('Century Gothic', 15),fg_color='gray31', text=content,corner_radius=6).place(x=2)
        root.after(0, update_gui)

    while True:
        if event.is_set():
            break
        try:
            # 超时等待消息,避免永久阻塞
            msg = msg_recieve.get(timeout=0.1)
            if msg.get_class_code() == 1 and msg.get_sender_id() == chat_id:
                content = msg.get_text()
                print_msg(content)
        except queue.Empty:
            continue

5. 设置守护线程,避免残留资源

创建接收线程时设置daemon=True,确保程序退出时线程能自动终止,避免残留线程占用资源:

recieve_thread = threading.Thread(target=receiving_messages, args=(event,data[1],messages_frame), daemon=True)
recieve_thread.start()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 11:42:07