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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 20:57:00