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

如何使用Tkinter控制多进程并解决启动后GUI无响应问题

问题修复方案

问题根源

  • 代码中两处阻塞操作占住了tkinter主线程,导致GUI无响应:
    1. ProcessPoolExecutor的with上下文管理器会阻塞到所有提交的任务执行完成才会退出上下文,单次就会卡住20秒
    2. 直接调用k.result()、t.result()是同步阻塞方法,会一直等待任务返回结果

修复思路

  • 取消with上下文管理进程池,改为全局持有进程池实例,避免每次执行都重建、销毁进程池
  • 用Future.add_done_callback()异步接收任务结果,不阻塞主线程
  • 新增运行状态标记、定时器id存储,支持启停控制
  • UI更新操作全部调度到tkinter主线程执行,避免线程安全问题

修改后完整代码

import tkinter as tk
import concurrent.futures

# 此处保留你原有Page类、kansai_url、tokyo_url的定义代码
kansai = Page(kansai_url)
tokyo = Page(tokyo_url)

# 全局变量存储运行状态
executor = concurrent.futures.ProcessPoolExecutor()
is_running = False
loop_timer_id = None

def update_kansai_result(future):
    # 调度到主线程更新UI
    root.after(0, lambda: setattr(kansai_lbl, 'text', future.result()))

def update_tokyo_result(future):
    root.after(0, lambda: setattr(tokyo_lbl, 'text', future.result()))

def loop_main():
    global loop_timer_id
    if not is_running:
        return
    # 异步提交任务,无阻塞
    k = executor.submit(kansai.compare)
    t = executor.submit(tokyo.compare)
    # 绑定结果回调
    k.add_done_callback(update_kansai_result)
    t.add_done_callback(update_tokyo_result)
    # 设置下一次执行
    loop_timer_id = root.after(60000, loop_main)

def toggle_start_stop():
    global is_running
    if not is_running:
        # 启动定时任务
        is_running = True
        start_btn.config(text='Stop')
        loop_main()
    else:
        # 停止定时任务
        is_running = False
        start_btn.config(text='Start')
        if loop_timer_id:
            root.after_cancel(loop_timer_id)

def quit_app():
    # 先关闭进程池,再销毁窗口,避免产生孤儿进程
    executor.shutdown(wait=False, cancel_futures=True)
    root.destroy()

if __name__ == '__main__':

    root = tk.Tk()

    # --buttons--
    start_btn = tk.Button(root, text='Start', command=toggle_start_stop, font='Raleway', bg='#20bebe', fg='white', height=2,
                          width=10)
    start_btn.grid(column=1, row=3)

    refresh_btn = tk.Button(root, text='Refresh', font='Raleway', bg='#20bebe', fg='white', height=2, width=10,
                            command=lambda: [executor.submit(kansai.compare).add_done_callback(update_kansai_result), 
                                            executor.submit(tokyo.compare).add_done_callback(update_tokyo_result)])
    refresh_btn.grid(column=2, row=3)

    quit_btn = tk.Button(root, text='Quit', command=quit_app, font='Raleway', bg='#20bebe', fg='white', height=2,
                         width=10)
    quit_btn.grid(column=3, row=3)

    # -- instruction --
    kansai_name_lbl = tk.Label(root, text='Kansai', font='Raleway')
    kansai_name_lbl.grid(column=1, row=0)
    tokyo_name_lbl = tk.Label(root, text='Tokyo', font='Raleway')
    tokyo_name_lbl.grid(column=3, row=0)

    kansai_lbl = tk.Label(root)
    kansai_lbl.grid(column=1, row=1)

    tokyo_lbl = tk.Label(root)
    tokyo_lbl.grid(column=3, row=1)

    root.mainloop()

效果说明

  • 所有耗时任务都异步提交到进程池执行,主线程全程不会阻塞,任何时候都可以点击Stop或者Quit按钮
  • Quit按钮触发时会直接取消所有待执行的进程池任务,不需要等待当前运行的任务完成就可以退出
  • 原Start按钮已经修改为启停切换按钮,点击即可随时暂停/恢复定时任务

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 04:15:03