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
相关产品推荐
相关产品推荐

