为何Python程序点击按钮后启动自身新实例(打包后异常)
问题描述
使用auto-py-to-exe打包CustomTkinter应用后,点击信息按钮时程序会进入无响应状态,随后自动启动应用的新实例,旧实例仍在后台运行。在IDE中运行时一切正常,已修复线程相关错误、重复变量/函数命名问题,stop_thread函数为后期添加,并非问题诱因。
按钮代码
info_but = customtkinter.CTkButton(menu,text="", font=custom_font, image=info_def,bg_color="#FDF6E4",fg_color="#FDF6E4",hover_color="#3b444b", width=70, command= info_menu,height=70) info_but.place(x=20, y=220)
触发函数代码
def info_menu(): global update_label, gpu_label stop_thread() # Beispielverwendung os_ver = f"{uname().system} {uname().release}" # get_gpu_models() threaded_check_for_new_version(repo_owner, repo_name, current_version) proc_info = cpuinfo.get_cpu_info() # RAM Informationen ram = psutil.virtual_memory() for child in frame.winfo_children(): child.destroy() content_frame = customtkinter.CTkFrame(frame, width=880, height=600) content_frame.place(x=0, y=0) label = customtkinter.CTkLabel(content_frame, text=f"General Information:",anchor="w", justify="left", width=200, height=30, font=custom_font2) label.grid(row=0, column=0, padx=(10,10),pady=(10,30)) label = customtkinter.CTkLabel(content_frame, text=f"App version:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=2, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"{current_version}",anchor="w", width=130, height=20,font=custom_font) label.grid(row=2, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"Build date:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=3, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"05/2024",anchor="w", width=130, height=20,font=custom_font) label.grid(row=3, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"Publisher:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=4, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"",anchor="w", width=130, height=20,font=custom_font) label.grid(row=4, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"Update avaiable:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=5, column=0, padx=(150,10),pady=10) update_label = customtkinter.CTkLabel(content_frame, text=f"Working" ,anchor="w", width=130, height=20,font=custom_font) update_label.grid(row=5, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"Authentic:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=6, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"Yes",anchor="w", width=130, height=30,font=custom_font) label.grid(row=6, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"System Information:",anchor="w", justify="left", width=200, height=30, font=custom_font2) label.grid(row=7, column=0, padx=(10,10),pady=(20,10)) label = customtkinter.CTkLabel(content_frame, text=f"CPU Model:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=10, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"{'{0}'.format(proc_info['brand_raw'])}",anchor="w", width=130, height=20,font=custom_font,wraplength=130) label.grid(row=10, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"Architecture:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=9, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"{'{0}'.format(proc_info['arch'])}",anchor="w", width=130, height=20,font=custom_font) label.grid(row=9, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"OS:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=8, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"{os_ver}",anchor="w", width=130, height=20,font=custom_font) label.grid(row=8, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"GPU:",anchor="w", width=130, justify="left", height=20,font=custom_font) label.grid(row=11, column=0, padx=(150,10),pady=10) gpu_label = customtkinter.CTkLabel(content_frame, text=f"Working",anchor="w", width=130, height=20,font=custom_font) gpu_label.grid(row=11, column=1, padx=360,pady=10) label = customtkinter.CTkLabel(content_frame, text=f"Avaiable RAM:",anchor="nw", width=130, justify="left", height=20,font=custom_font) label.grid(row=12, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"{str(ram.total / 1024**3)[:4]} GB",anchor="w", width=130, height=20,font=custom_font) label.grid(row=12, column=1, padx=360,pady=(10,10)) label = customtkinter.CTkLabel(content_frame, text=f"",anchor="nw", width=130, justify="left", height=20,font=custom_font) label.grid(row=13, column=0, padx=(150,10),pady=10) label = customtkinter.CTkLabel(content_frame, text=f"",anchor="w", width=130, height=20,font=custom_font) label.grid(row=13, column=1, padx=360,pady=(10,10)) threaded_get_gpu_models(gpu_label)
关联函数代码
def fetch_latest_version(repo_owner, repo_name, current_version): global cached_result url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" response = requests.get(url) if response.status_code == 200: latest_version = response.json().get('tag_name') comparison_result = semver.compare(current_version, latest_version) if comparison_result < 0: cached_result = [True, latest_version] elif comparison_result == 0: cached_result = [False, latest_version] else: cached_result = [False, current_version] # Deine Version ist neuer else: cached_result = [False, None] update_label_text() def update_label_text(): global cached_result, update_label if cached_result[0]: update_label.configure(text="Yes") else: if cached_result[1] is None: update_label.configure(text="Error") else: update_label.configure(text="No") root.update() def check_for_new_version(repo_owner, repo_name, current_version): global cached_result, cache_lock root.update() with cache_lock: if cached_result[1] is None: fetch_latest_version(repo_owner, repo_name, current_version) else: update_label_text() def threaded_check_for_new_version(repo_owner, repo_name, current_version): thread = threading.Thread(target=check_for_new_version, args=(repo_owner, repo_name, current_version)) thread.start() def fetch_gpu_models(result): try: gpus = GPUtil.getGPUs() gpu_models = set(gpu.name for gpu in gpus) result.append(list(gpu_models) if gpu_models else ["Unknown"]) except Exception as e: result.append(["Unknown"]) update_model_label(gpu_label,result[0]) def update_model_label(gpu_label,gpu_models): gpu_label.configure(text=f"{gpu_models[0]}") root.update() def get_gpu_models(gpu_label): result = [] fetch_gpu_models(result) return result[0] if result else ["Unknown"] def threaded_get_gpu_models(gpu_label): thread = threading.Thread(target=get_gpu_models, args=(gpu_label,)) thread.start()
解决方案
1. 调整auto-py-to-exe打包配置
- 放弃**单文件(One File)打包模式,改用目录(Directory)**模式。单文件模式下,程序运行时会解压到临时目录,部分依赖库(如GPUtil、cpuinfo)可能因文件访问冲突导致异常,触发程序重启。
- 确保所有依赖库被正确打包:在auto-py-to-exe的
Additional Files选项中,添加应用依赖的资源文件(如info_def图片),避免打包后资源缺失。
2. 修复线程与GUI更新逻辑
CustomTkinter的GUI组件必须在主线程中更新,非主线程调用root.update()会引发线程安全问题,在打包后可能导致程序崩溃重启。修改以下代码:
(1)修改版本检查相关函数
def fetch_latest_version(repo_owner, repo_name, current_version): global cached_result url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" response = requests.get(url) if response.status_code == 200: latest_version = response.json().get('tag_name') comparison_result = semver.compare(current_version, latest_version) if comparison_result < 0: cached_result = [True, latest_version] elif comparison_result == 0: cached_result = [False, latest_version] else: cached_result = [False, current_version] else: cached_result = [False, None] # 使用root.after在主线程更新GUI root.after(0, update_label_text) def update_label_text(): global cached_result, update_label if cached_result[0]: update_label.configure(text="Yes") else: if cached_result[1] is None: update_label.configure(text="Error") else: update_label.configure(text="No") # 移除root.update(),CustomTkinter会自动处理GUI刷新 def check_for_new_version(repo_owner, repo_name, current_version): global cached_result, cache_lock with cache_lock: if cached_result[1] is None: fetch_latest_version(repo_owner, repo_name, current_version) else: root.after(0, update_label_text) # 移除root.update()
(2)修改GPU信息获取相关函数
def fetch_gpu_models(result): try: gpus = GPUtil.getGPUs() gpu_models = set(gpu.name for gpu in gpus) result.append(list(gpu_models) if gpu_models else ["Unknown"]) except Exception as e: result.append(["Unknown"]) # 使用root.after在主线程更新GUI root.after(0, update_model_label, gpu_label, result[0]) def update_model_label(gpu_label,gpu_models): gpu_label.configure(text=f"{gpu_models[0]}") # 移除root.update()
3. 检查stop_thread函数实现
确保stop_thread采用安全的线程终止方式(如使用threading.Event标志),而非强制终止线程。强制终止线程可能导致资源泄漏,在打包环境下引发程序异常重启。示例实现:
# 全局线程终止标志 stop_event = threading.Event() def stop_thread(): stop_event.set() # 在线程函数中检查标志 def check_for_new_version(repo_owner, repo_name, current_version): global cached_result, cache_lock if stop_event.is_set(): return with cache_lock: if cached_result[1] is None and not stop_event.is_set(): fetch_latest_version(repo_owner, repo_name, current_version) elif not stop_event.is_set(): root.after(0, update_label_text)
4. 添加单实例运行检测
打包后程序异常重启会导致多实例并存,可通过文件锁实现单实例运行,避免重复启动:
import os import sys import tempfile from filelock import FileLock def check_single_instance(): lock_file = os.path.join(tempfile.gettempdir(), "my_app.lock") lock = FileLock(lock_file) try: lock.acquire(timeout=0) except: sys.exit(0) # 在程序启动时调用 check_single_instance()
内容的提问来源于stack exchange,提问作者snoms
相关产品推荐
相关产品推荐

