如何使用Tkinter控制多进程并解决启动后GUI无响应问题
问题修复方案
问题根源
- 代码中两处阻塞操作占住了tkinter主线程,导致GUI无响应:
ProcessPoolExecutor的with上下文管理器会阻塞到所有提交的任务执行完成才会退出上下文,单次就会卡住20秒- 直接调用
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
相关产品推荐
相关产品推荐

