首先给出代码,后面说明为什么会有这样的需求及解决思路。
import time
from multiprocessing import Process, freeze_support
from tkinter import *
from tkinter.ttk import Frame
import threading
def main(x):
for i in range(x, -1, -1):
print(i)
time.sleep(1)
def processStop(p, btn):
while p.is_alive():
pass
btn.config(state='normal')
def btn():
B1.config(state='disabled')
p = Process(target=main, args=(int(E1.get()),))
p.start()
t = threading.Thread(target=processStop, args=(p, B1)) # 子线程用于监控进程p是否结束
t.start()
if __name__ == "__main__":
freeze_support()
root = Tk()
root.resizable(width=False, height=False) # 阻止Python GUI的大小调整
page = Frame(root, padding=(5, 20, 10, 10))
page.pack()
Label(page, text='倒计时').grid(row=1, column=1, padx=20, pady=10)
E1 = Entry(page)
E1.grid(row=1, column=2, padx=20, pady=10)
E1.insert(0, '10')
B1 = Button(page, text='开 始', command=btn)
B1.grid(row=5, column=1, columnspan=2, pady=10)
root.mainloop()
在某些情况下,Tkinter 界面中的按钮仅作为一些功能(下面或称为函数)的启动按钮,而完成这些功能需要花费一些时间,如果在 Tk 主进程中等待函数执行完毕则会造成进程阻塞,导致界面无法操作,这时如果界面有其他功能需要操作就无法进行了,如下图所示。
因此需要将这种比较耗时的函数交给其他进程或线程来执行(Tkinter 与多线程结合的例子网上比较多,这里不进行赘述,并且多线程并不是真正的并行,进程才是,这里只讨论多进程。),我们将其交个子进程(下面称为子进程a)来执行。
接下来,我们只想要让按钮执行一次(只启动一个子进程a,否则会浪费系统资源甚至报错),等待子进程a执行结束时才允许再次启动,因此我们需要在子进程a执行时将按钮禁用掉,并在子进程a执行结束后使之恢复正常。禁用按钮非常好实现,在子进程a启动前设置按钮状态为 disabled,但是恢复正常比较麻烦。
上面提到的两个问题其中第一个属于系统规则,我们无法解决,那我们就对第二个下手。
上面也提到了,进程阻塞是因为主进程在完成一些比较费时的功能,此时无法响应其他操作,所以第二个问题我们将其交给其他进程或线程完成。
但是如果交给的是子进程(下面称为子进程b),子进程b确实可以判断子进程a是否存活,并且也不会阻塞 Tk 界面进程的执行,但是子进程b依旧不能更改 Tk 界面进程中按钮的状态。
我们将循环判断任务交给子线程时,因为该子线程与 Tk 界面同属一个进程,当子线程判断到子进程a执行结束后,即可直接修改 Tk 界面中的控件状态,并且子线程的执行也不会阻塞 Tk 界面进程(只要不设置t.join()即可)。