使用tkinter的after函数定时更新socket数据时程序崩溃如何解决?
崩溃原因
- 阻塞式recv调用卡住GUI主线程:默认创建的socket是阻塞模式,
sock.recv(2000)执行时如果服务端还没有返回数据,会一直阻塞等待,占用tkinter唯一的主线程,导致界面事件循环无法正常运行,直接无响应崩溃。 - 重复创建UI组件:
show_json_info方法每次执行都会新建Label和Text组件插入到页面,多次调度后页面组件数量会无限增长,最终导致内存占用过高、tkinter渲染异常崩溃。 - 无异常捕获逻辑:如果出现socket断开、收到的JSON包不完整/格式错误、服务端主动断连等情况,
recv或json.loads会直接抛出未处理的异常,导致程序直接退出。
可行解决方案
- 把socket收包逻辑移到独立子线程执行:不要在主线程运行任何阻塞操作,子线程收到数据后存入线程安全的
queue.Queue队列,主线程通过after定时从队列取数据更新UI(tkinter所有UI操作必须在主线程执行,禁止子线程直接修改组件)。 - 提前创建UI组件复用:在
SystemPage初始化时就创建好展示用的Text组件,每次更新数据前先清空原有内容,再写入新数据,不要重复新建组件。 - 补全异常捕获逻辑:对socket操作、JSON解析过程加
try-except捕获常见异常,捕获到异常后关闭无效socket、提示错误信息、切回连接页面,避免程序直接崩溃。
核心修改代码示例
# 新增导入 import threading import queue class MainGUI(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) # 新增队列用来线程间传数据 self.info_queue = queue.Queue() self.sock = None self.update_running = False container = tk.Frame(self) container.pack(side="top", fill="both", expand=True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) self.frames = {} for F in (ConnectionPage, SystemPage): frame = F(container, self) self.frames[F] = frame frame.grid(row=0, column=0, sticky="nsew") self.show_frame(ConnectionPage) # 启动定时读队列更新UI的任务 self.process_queue() def process_queue(self): # 从队列取数据更新UI while not self.info_queue.empty(): try: JsonDict = self.info_queue.get_nowait() # 直接用SystemPage提前创建好的Text组件更新 frame = self.frames[SystemPage] frame.sys_text.delete("1.0", tk.END) frame.sys_text.insert("end","System: "+JsonDict["System"]+'\n') frame.sys_text.insert("end","NodeName: " + JsonDict["NodeName"]+'\n') frame.sys_text.insert("end","Release: "+JsonDict["Release"]+'\n') frame.sys_text.insert("end","Version: "+JsonDict["Version"]+'\n') frame.sys_text.insert("end","Machine: "+JsonDict["Machine"]+'\n') frame.sys_text.insert("end","Processor: "+JsonDict["Processor"]+'\n') except Exception as e: pass self.after(100, self.process_queue) # 子线程收数据逻辑 def recv_data_thread(self, sock): while self.update_running: try: packet = sock.recv(2000).decode() if not packet: break JsonDict = json.loads(packet) self.info_queue.put(JsonDict) time.sleep(2.5) except Exception as e: break # 异常或者断开后切回连接页 self.update_running = False sock.close() self.show_frame(ConnectionPage) self.show_error(ConnectionPage, "连接已断开") # 原有其他方法保留即可 # ... 省略原有show_frame、show_error等方法 ... class ConnectionPage(tk.Frame): def InitiateConnection(self,controller, Ip,Port): sock = Client.CheckAddress(Ip.get(),int(Port.get())) if sock: controller.sock = sock controller.update_running = True # 启动收数据子线程 threading.Thread(target=controller.recv_data_thread, args=(sock,), daemon=True).start() controller.show_frame(SystemPage) else: controller.show_error(ConnectionPage, "Wrong Ip or port") # 原有其他初始化逻辑保留即可 # ... 省略原有代码 ... class SystemPage(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self, parent) BackToConnectionButton = tk.Button(self, text="Back to Connection Page",command=lambda: self.back_to_conn(controller)) BackToConnectionButton.pack() # 提前创建好UI组件 SysInfoLabel = tk.Label(self, text="System Info", font=LARGE_FONT) SysInfoLabel.pack(pady=10, padx=10) self.sys_text = tk.Text(self, font=LARGE_FONT) self.sys_text.pack() def back_to_conn(self, controller): # 切页时停止收数据线程 if controller.update_running: controller.update_running = False controller.sock.close() controller.show_frame(ConnectionPage)
内容的提问来源于stack exchange,提问作者user9625406
相关产品推荐
相关产品推荐

