Python中tkinter如何退出,具体是在多线程下面如何退出

弄了一个 windown10 后台循环读文件程序。程序运行后最小化到桌面右下角。在右下角图标上面有 3 个功能菜单,一个显示“配置参数”界面,一个显示“运行列表”界面,一个退出按钮

“配置参数”界面可以正常显示关闭。 “运行列表”界面关闭后,点击桌面右下角退出按钮后,程序退出隔一会儿会报错,报错内容如下:

Exception ignored in: <bound method Variable.__del__ of <tkinter.StringVar object at 0x03DFAEF0>>
Traceback (most recent call last):
  File "C:\Users\85447\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 329, in __del__
    if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop
Tcl_AsyncDelete: async handler deleted by the wrong thread

此段代码可能是产生报错原因

instrserial_button = Entry(self, textvariable=self.dec_name_dict[dish], width=15,
                                           bg='white',state='readonly')#.grid(row=j, column=3)

使用 sysTrayIcon 做的桌面右下角图标,主要代码摘抄这里

def showRegSet_sys(sysTrayIcon):
    try:
        title = u'reg instrserial(NET)'
        w1hd2 = win32gui.FindWindow(0, title)
        # w1hd = 0
        print ("w1hd2",w1hd2)
        if w1hd2 ==0:
            #print ("w1hd==0")
            Regroot = Tk()
            # decroot.wm_attributes('-topmost',1)
            Regroot.geometry("400x300")
            app2 = regWindow(Regroot)
            def on_focus_out(event):
                if event.widget == Regroot:
                    Regroot.destroy()
                    # Regroot.quit
            Regroot.bind("<FocusOut>", on_focus_out)
            Regroot.mainloop()
        else:
            win32gui.SetForegroundWindow(w1hd2)
    except Exception as E:
        pass
        print(E)
class regWindow(Frame):#弹出桌面框显示类
    """定义一个 window 类,window 的父类是 frame,这样 window 就用了 frames 的属性和功能"""
    def __init__(self,master=NONE):
        #__int()__这里是一个构造函数
        Frame.__init__(self,master)
        self.master=master
        # self.reg_instr = p_query(
        #     """SELECT  instrid,instrserial,instrsn,register_dt,install_dt FROM lab_instrument1 order by instrid """)
        self.reg_instr = [(1, '230', '1mvpwtpkdv'), (2, '500', 'yxc3juy5p3')]
        self.initwindow()
        # self.initwindow2()
def initwindow(self):#tkinter 模块添加按钮,相对于长提多了如下部分代码
    #设置窗体标题
    self.master.title("reg instrserial(NET)")
    self.sv = StringVar()
    self.sv2 = StringVar()
    self.pack(side="top", fill="x")
    # dishes=[("1","23"),("2","gsdf"),("3","fdgsrd"),("4","dfg")]
    self.order = {}
    self.add_buttons = {}
    self.instrserial_button_dict={}
    self.price_labels = {}
    self.order_labels = {}
    self.state_dict = {}
    self.svv2 = {}
    self.svv = {}
    self.dec_name_dict = {}
    j = 2
    self.en6 = StringVar()
    nianling2 = Entry(self, width=25, bg='white', state='readonly', textvariable=self.en6).grid(row=j-1, column=1,  columnspan=2)
    self.en6.set("uuu")
    Button_chaxun = Button(self, text='关闭', command=self.clinet_exit).grid(row=j-1, column=4)
    # Button_chaxun = Button(self, text='关闭 2', command=self.master.quit).grid(row=j - 1, column=3)
    for dish in self.reg_instr:

        self.svv[dish[0]] = StringVar()
        self.dec_name_dict[dish[0]] = StringVar()
    Label(self, text="ID").grid(row=j, column=1)
    Label(self, text="码").grid(row=j, column=2)
    for i in self.reg_instr:
        j += 2
        print("j:",j)
        # dish, price = dish_data[0], dish_data[1].encode('latin-1').decode('gbk')
        instrid, instrserial, instrsn = i
        dish = i[0]
        name = i[1].encode('latin-1').decode('gbk')
        self.state_dict[dish] = 'normal'
        self.svv2[dish] = dish
        self.dec_name_dict[dish].set(name)
        sn_code = 10
        if sn_code &gt; 0:
            b = Entry(self, width=15, bg='white', state='readonly')#.grid(row=j, column=2)
            instrserial_button = Entry(self, textvariable=self.dec_name_dict[dish], width=15,
                                       bg='white',state='readonly')#.grid(row=j, column=3)
        else:
            pass
            # b = Entry(self, width=15, bg='white', state='readonly')
            # b = Entry(self, textvariable=self.svv[dish], validate="focusout", width=15, bg='white', state=self.state_dict[dish],
            #           validatecommand=lambda d=self.svv[dish], k=dish: self.add_dish_to_order(d, self.state_dict[dish], k))
            # instrserial_button = Entry(self, textvariable=self.dec_name_dict[dish], validate="focusout", width=15,
            #                        bg='white',state=self.state_dict[dish],
            #                        validatecommand=lambda d=self.dec_name_dict[dish],instrid=dish, k=name: self.add_dec_name(d,instrid,k))#.grid(row=j, column=3)
        # # b['state']  = 'readonly'svv2[dish]
        # #  print(dish)
        self.add_buttons[dish] = b
        self.add_buttons[dish].grid(row=j, column=2)
        self.instrserial_button_dict[dish]=instrserial_button
        self.instrserial_button_dict[dish].grid(row=j, column=3)

def add_dish_to_order(self, d, dish, i):
    return True

def add_dec_name(self, d, instrid, i):
    return True

def clinet_exit(self):
    # self.master.deiconify()
    # print('showRegSet()')
    self.master.destroy()

menu_options = ((‘显示(S)’, None,showDecSet), (‘other’, None, ((‘解码注册’, None, showRegSet_sys), )) )

参考代码在这里 https://paste.ubuntu.com/p/VzTV26tcvc/


Python中tkinter如何退出,具体是在多线程下面如何退出

2 回复

在tkinter多线程环境下安全退出,核心是让主线程处理GUI操作。用threading模块创建后台线程,通过queue.Queue传递消息,主线程用after方法轮询队列。

import tkinter as tk
import threading
import queue
import time

class App:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("多线程退出示例")
        
        # 消息队列
        self.queue = queue.Queue()
        
        # 界面组件
        self.label = tk.Label(self.root, text="等待线程...")
        self.label.pack(pady=20)
        
        self.btn_start = tk.Button(self.root, text="启动线程", command=self.start_thread)
        self.btn_start.pack(pady=5)
        
        self.btn_stop = tk.Button(self.root, text="安全退出", command=self.safe_quit)
        self.btn_stop.pack(pady=5)
        
        # 启动队列检查
        self.check_queue()
        
        # 线程控制标志
        self.thread_running = False
        
    def start_thread(self):
        """启动后台线程"""
        if not self.thread_running:
            self.thread_running = True
            self.thread = threading.Thread(target=self.background_task, daemon=True)
            self.thread.start()
            self.label.config(text="线程运行中...")
    
    def background_task(self):
        """后台线程任务"""
        for i in range(10):
            if not self.thread_running:
                break
            time.sleep(1)
            # 通过队列发送消息到主线程
            self.queue.put(f"计数: {i+1}")
        self.queue.put("THREAD_DONE")
    
    def check_queue(self):
        """主线程检查消息队列"""
        try:
            while True:
                msg = self.queue.get_nowait()
                if msg == "THREAD_DONE":
                    self.label.config(text="线程完成")
                    self.thread_running = False
                else:
                    self.label.config(text=msg)
        except queue.Empty:
            pass
        finally:
            # 每100ms检查一次队列
            self.root.after(100, self.check_queue)
    
    def safe_quit(self):
        """安全退出程序"""
        self.thread_running = False
        self.root.quit()  # 停止mainloop
        self.root.destroy()  # 销毁窗口
    
    def run(self):
        self.root.mainloop()

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

关键点:

  1. daemon线程:设置daemon=True让线程随主程序退出
  2. 队列通信:用queue.Queue线程安全传递数据
  3. after轮询:主线程定期检查队列,避免阻塞GUI
  4. 控制标志:用thread_running标志控制线程退出
  5. 正确退出:先quit()停止mainloop,再destroy()销毁窗口

不要在线程中直接操作GUI组件,所有GUI更新都通过队列让主线程处理。

总结:用队列通信,主线程控制退出。


Regroot.destroy() 已经销毁了,为啥还会提示 main thread is not in main loop

回到顶部