使用tkinter多进程时,如何通过函数更新Label控件值?
问题描述
- 程序通过多进程监控指定目录下的文件夹,需要在
process2执行过程中更新主界面的currentUserProcess2标签,但尝试全局变量、传递控件参数均失败,提示“变量未定义”。 - 所有tkinter界面代码都写在
if __name__ == '__main__'块中,不确定是否符合最佳实践。
原始代码
import os import sched, time import multiprocessing as mp from tkinter import* from tkinter import ttk import time from tkinter import messagebox def process1(): s = sched.scheduler(time.time, time.sleep) def checkFolders(sc): target = 'C:\\Temp' report = open('log.txt', 'a') for root, dirnames, files in os.walk(target): dirnames[:] = [d for d in dirnames] for x in files: if x.startswith('info'): rootSplit = (root.split("\\\\")) user = rootSplit[2] serviceFolder = rootSplit[4] serviceFolderSplit = (serviceFolder.split("_")) time = serviceFolderSplit[1] service = serviceFolderSplit[3] log = user + "_" + service + "_" + time + "\n" match service: case "1": #do some stuff report.write(log) case "2": #do some stuff report.write(log) case "3": #do some stuff report.write(log) report.close() sc.enter(60, 1, checkFolders, (sc,)) s.enter(1, 1, checkFolders, (s,)) s.run() def process2(): s = sched.scheduler(time.time, time.sleep) def checkFolders(sc): target = 'C:\\Temp' report = open('log.txt', 'a') for root, dirnames, files in os.walk(target): dirnames[:] = [d for d in dirnames] for x in files: if x.startswith('info'): rootSplit = (root.split("\\\\")) user = rootSplit[2] serviceFolder = rootSplit[4] serviceFolderSplit = (serviceFolder.split("_")) time = serviceFolderSplit[1] service = serviceFolderSplit[3] log = user + "_" + service + "_" + time + "\n" match service: case "S04": #do some stuff report.write(log) case "S05": #do some stuff report.write(log) case "S06": #do some stuff report.write(log) case "S07": #do some stuff report.write(log) report.write(log) updateCurrentUserP2(log) report.close() sc.enter(60, 1, checkFolders, (sc,)) s.enter(1, 1, checkFolders, (s,)) s.run() def updateCurrentUserP2(log): currentUserProcess2.config(text=log) def startProcesses(): global p1 global p2 p1 = mp.Process(target = process1) p2 = mp.Process(target = process2) p1.start() p2.start() buttonStart["state"] = DISABLED buttonStop["state"] = NORMAL def stopProcesses(): p1.terminate() p2.terminate() buttonStart["state"] = NORMAL buttonStop["state"] = DISABLED def on_close(): response=messagebox.askyesno('Confirm','Stop services and end program?') if response: try: stopProcesses() except: "There is not active service" root.destroy() if __name__ == '__main__': root = Tk() ###############Interface################################################## root.geometry('520x270') root.title("Services") frameP1 = LabelFrame(root, text="Process1", font=("Helvetica", 14, "bold"), bd=0) frameP1.grid(row =1, column=0, padx=12) currentUserProcess1 = Label(frameP1, text="Current User p1", font=("Helvetica", 10, "bold")) currentUserProcess1.grid(row = 3, column = 0, columnspan = 2) separator = ttk.Separator(root) separator.grid(row = 2, column=0, sticky="ew", padx=20) frameP2 = LabelFrame(root, text="Process2", font=("Helvetica", 14, "bold"), bd=0) frameP2.grid(row=3, column=0) currentUserProcess2 = Label(frameP2, text="Current User p2", font=("Helvetica", 10, "bold")) currentUserProcess2.pack() buttonStart = Button(root, text="Start Services", font=("Helvetica", 10, "bold"), command= startProcesses) buttonStart.grid(row = 4, column = 0, columnspan = 1, pady = 5) buttonStop = Button(root, text="Stop Services", font=("Helvetica", 10, "bold"), command=stopProcesses) buttonStop.grid(row = 4, column = 1, columnspan = 1, pady = 5) buttonStop["state"] = DISABLED #################################################################################### startProcesses() root.protocol('WM_DELETE_WINDOW',on_close) root.mainloop()
解决方案
1. 解决多进程更新tkinter标签的问题
multiprocessing创建的子进程拥有独立内存空间,无法直接访问主进程中的tkinter控件,这是报错的核心原因。需要通过**进程间通信(IPC)**传递数据,由主进程负责更新界面:
修改后的核心代码
# 全局定义进程间通信队列 queue_p2 = mp.Queue() def process2(queue): # 接收队列作为参数 s = sched.scheduler(time.time, time.sleep) def checkFolders(sc): target = 'C:\\Temp' report = open('log.txt', 'a') for root, dirnames, files in os.walk(target): dirnames[:] = [d for d in dirnames] for x in files: if x.startswith('info'): rootSplit = (root.split("\\\\")) user = rootSplit[2] serviceFolder = rootSplit[4] serviceFolderSplit = (serviceFolder.split("_")) time_val = serviceFolderSplit[1] # 避免和模块time重名 service = serviceFolderSplit[3] log = user + "_" + service + "_" + time_val + "\n" match service: case "S04": #do some stuff report.write(log) case "S05": #do some stuff report.write(log) case "S06": #do some stuff report.write(log) case "S07": #do some stuff report.write(log) report.write(log) # 将日志放入队列,由主进程更新界面 queue.put(log) report.close() sc.enter(60, 1, checkFolders, (sc,)) s.enter(1, 1, checkFolders, (s,)) s.run() def update_current_user_p2(): # 主进程轮询队列更新标签 while not queue_p2.empty(): log = queue_p2.get() currentUserProcess2.config(text=log) # 每100ms轮询一次,避免阻塞主循环 root.after(100, update_current_user_p2) def startProcesses(): global p1, p2 p1 = mp.Process(target=process1) p2 = mp.Process(target=process2, args=(queue_p2,)) # 传递队列参数 p1.start() p2.start() buttonStart["state"] = DISABLED buttonStop["state"] = NORMAL # 启动主进程的界面更新轮询 update_current_user_p2()
修改说明
- 使用
mp.Queue作为子进程和主进程的通信桥梁,子进程只负责生产数据,不直接操作界面。 - 主进程通过
root.after()定时轮询队列,取出数据后更新标签,符合tkinter单线程更新界面的规则。 - 修复了变量名冲突(将
time改为time_val,避免和导入的time模块重名)。
2. tkinter代码放在if __name__ == '__main__'中的实践
这是完全符合最佳实践的做法:
- Windows系统中,multiprocessing会通过重新导入模块创建子进程,如果tkinter代码不在该块中,子进程会重复创建窗口,导致程序崩溃。
- 该块明确标识程序入口,代码结构更清晰,符合Python脚本编写规范。
内容的提问来源于stack exchange,提问作者Agusms
相关产品推荐
相关产品推荐

