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

Python GUI多进程实现报错求助:无法序列化_tkinter.tkapp对象

解决Tkinter/CustomTkinter多进程中的Pickle与GUI更新问题

错误原因

  1. Tkinter对象无法序列化:将类方法(self.loading、self.getGraph)作为多进程target时,Python会尝试序列化整个App实例(self),但Tkinter的tkapp对象不支持pickle,直接触发can't pickle _tkinter.tkapp objects错误。
  2. 子进程不能操作GUI:Tkinter的GUI必须在主线程中更新,子进程直接修改self.tab_a1等元素会导致线程安全问题,进而引发EOFError(子进程异常退出后,主进程读取输出时无数据)。

解决方案

  • 用**进程间队列(Queue)**实现子进程与主线程的数据传递,子进程只处理耗时计算,主线程负责GUI更新。
  • 将耗时任务封装为独立函数,避免携带GUI实例引用。
  • 用Tkinter的after()方法定期监听队列,触发GUI更新。

修改后的完整代码

import tkinter
import multiprocessing as mp
from multiprocessing import Queue
from tkinter import filedialog
import customtkinter
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from CV2_Frame_Extraction import *
from Color_Extractor_CT import *
from graph import *
import os

customtkinter.set_appearance_mode("System")
customtkinter.set_default_color_theme("blue")

# 独立的耗时任务函数,不引用GUI实例
def process_video(decision, queue):
    try:
        # 发送加载状态
        queue.put({"type": "loading"})
        
        # 帧提取
        frame_extraction('\\'.join(decision))
        
        # 颜色提取
        colors = ["Red", "Green", "Blue"]
        red = []
        green = []
        blue = []
        
        files = os.listdir('Frames')
        sorted_files = [file.replace('f_', '') for file in files]
        sorted_files = sorted(sorted_files, key=lambda x: int(os.path.splitext(x)[0]))
        
        for frame in sorted_files:          
            rgb = color_extraction(f'Frames/f_{frame}')
            r, g, b = rgb[0]
            red.append(r)
            green.append(g)
            blue.append(b)
            print(f'Frame: {frame} RGB:{rgb[0]}')
        
        frames = [(int(os.path.splitext(x)[0]))*20 for x in sorted_files]
        # 生成图表数据(而非直接创建Canvas)
        figs = generate_graph(red, green, blue, frames)
        
        # 发送结果数据
        queue.put({"type": "result", "figs": figs, "colors": colors})
    except Exception as e:
        queue.put({"type": "error", "msg": str(e)})

class App(customtkinter.CTk):
    btn_list = []
    def __init__(self):
        super().__init__()

        # Window Configuration
        self.title("Plasma Analysis")
        self.geometry("1100x580")

        # Layout Configuration
        self.grid_rowconfigure((0, 1, 2), weight=1)

        # Side Bar
        self.sb_frame = customtkinter.CTkFrame(self, width=140, corner_radius=0)
        self.sb_frame.grid(row=0, column=0, rowspan=4, sticky="nsew")
        self.sb_frame.grid_rowconfigure(4, weight=1)

        self.sb_new = customtkinter.CTkButton(self.sb_frame, text="Add Video", command=self.openfile, corner_radius=2)
        self.sb_new.grid(row=0, column=0, padx=20, pady=20)

        self.btn_frame = customtkinter.CTkFrame(self.sb_frame, width=140, height=400, corner_radius=0, fg_color="transparent", border_width=5)
        self.btn_frame.grid(row=1, column=0, rowspan=3)
    
        self.bf_label = customtkinter.CTkLabel(self.btn_frame, text="Analysis", font=customtkinter.CTkFont(size=20, weight="bold"), fg_color="transparent")
        self.bf_label.grid(row=0, column=0)

        self.appearance_mode_label = customtkinter.CTkLabel(self.sb_frame, text="Appearance Mode:", anchor="w")
        self.appearance_mode_label.grid(row=7, column=0, padx=20, pady=(10, 0))
        self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sb_frame, values=["Light", "Dark", "System"], command=self.change_appearance_mode_event)
        self.appearance_mode_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 10))
        self.scaling_label = customtkinter.CTkLabel(self.sb_frame, text="UI Scaling:", anchor="w")
        self.scaling_label.grid(row=9, column=0, padx=20, pady=(10, 0))
        self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sb_frame, values=["80%", "90%", "100%", "110%", "120%"], command=self.change_scaling_event)
        self.scaling_optionemenu.grid(row=10, column=0, padx=20, pady=(10, 20))

        # Creating Main Panel
        self.main_frame = customtkinter.CTkFrame(self, width=950, corner_radius=0, fg_color='transparent')
        self.main_frame.grid(row=0, column=1, rowspan=4, columnspan=4, sticky="nsew")
   
        self.tab_a1 = customtkinter.CTkTabview(self.main_frame, width=400, height=250, segmented_button_selected_color='gray', border_width=5)
        self.tab_a1.grid(row=0, column=0, padx=(30,50), pady=(15,15), sticky="nsew")
        self.tab_a1.grid_propagate(False)
        self.tab_a1.grid_columnconfigure(0, weight=0, uniform=1)
        self.tab_a1.grid_rowconfigure(0, weight=1, uniform=1)
        self.tab_a1.add("Color")

        self.tab_a2 = customtkinter.CTkTabview(self.main_frame, width=400, height=250, segmented_button_selected_color='gray', border_width=5)
        self.tab_a2.grid(row=0, column=1, padx=(0,50), pady=(15,15), sticky="nsew")
        self.tab_a2.add("Luminosity")

        self.tab_a3 = customtkinter.CTkTabview(self.main_frame, width=400, height=250, segmented_button_selected_color='gray', border_width=5)
        self.tab_a3.grid(row=1, column=0, padx=(30,50), pady=(15,15), sticky="nsew")
        self.tab_a3.add("Motion")

        self.tab_a4 = customtkinter.CTkTabview(self.main_frame, width=400, height=250, segmented_button_selected_color='gray', border_width=5)
        self.tab_a4.grid(row=1, column=1, padx=(0,50), pady=(15,15), sticky="nsew")
        self.tab_a4.add("Trend")

        # 初始化进程间队列
        self.queue = Queue()
        # 启动队列监听
        self.check_queue()

    def change_appearance_mode_event(self, new_appearance_mode: str):
        customtkinter.set_appearance_mode(new_appearance_mode)

    def change_scaling_event(self, new_scaling: str):
        new_scaling_float = int(new_scaling.replace("%", "")) / 100
        customtkinter.set_widget_scaling(new_scaling_float)

    def openfile(self):     
        decision = filedialog.askopenfilename().split('/')
        if not decision[0]:  # 用户取消选择
            return
        name = decision[-1].split('.')[0]

        # 添加视频按钮
        self.btn_list.append(customtkinter.CTkButton(self.btn_frame, text=name))     
        for x, btn in enumerate(self.btn_list):
            btn.grid(row=x+1, column=0, padx=10, pady=10)

        # 启动子进程处理视频,传递队列
        p = mp.Process(target=process_video, args=(decision, self.queue))
        p.start()

    def show_loading(self):
        # 主线程显示加载提示
        for tab in [self.tab_a2.tab('Luminosity'), self.tab_a3.tab('Motion'), self.tab_a4.tab('Trend')]:
            # 先清除原有内容
            for widget in tab.winfo_children():
                widget.destroy()
            lbl_load = customtkinter.CTkLabel(tab, text="Loading ...", font=customtkinter.CTkFont(size=20, weight="bold"))
            lbl_load.pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True, anchor='center')
        self.update()

    def update_graphs(self, figs, colors):
        # 主线程更新图表
        # 清除原有Color标签页内容
        self.tab_a1.delete("Color")
        # 添加新标签页并生成画布
        for idx, f in enumerate(figs):
            self.tab_a1.add(colors[idx])
            canvas = FigureCanvasTkAgg(f, self.tab_a1.tab(colors[idx]))
            f.tight_layout()
            canvas.draw()           
            canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True, anchor='center')
        self.tab_a1.configure(fg_color="White")

        # 清除加载提示
        for tab in [self.tab_a2.tab('Luminosity'), self.tab_a3.tab('Motion'), self.tab_a4.tab('Trend')]:
            for widget in tab.winfo_children():
                widget.destroy()

    def check_queue(self):
        # 定期检查队列,处理子进程消息
        while not self.queue.empty():
            msg = self.queue.get()
            if msg["type"] == "loading":
                self.show_loading()
            elif msg["type"] == "result":
                self.update_graphs(msg["figs"], msg["colors"])
            elif msg["type"] == "error":
                tkinter.messagebox.showerror("Error", msg["msg"])
        # 每100ms检查一次队列
        self.after(100, self.check_queue)

if __name__ == "__main__":
    app = App()
    app.mainloop()

关键修改点

  • 把loading和getGraph中的GUI操作移到主线程,子进程只负责计算并通过Queue传递状态/结果。
  • 使用独立函数process_video作为多进程target,避免传递GUI实例。
  • 用after()方法实现非阻塞的队列监听,确保GUI响应流畅。
  • 子进程中捕获异常并传递错误信息,避免静默崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 12:30:55