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

为何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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 23:14:54