【Python】Tkinter中判断子进程是否结束并改变控件状态

首先给出代码,后面说明为什么会有这样的需求及解决思路。

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 主进程中等待函数执行完毕则会造成进程阻塞,导致界面无法操作,这时如果界面有其他功能需要操作就无法进行了,如下图所示。
【Python】Tkinter中判断子进程是否结束并改变控件状态_第1张图片
因此需要将这种比较耗时的函数交给其他进程或线程来执行(Tkinter 与多线程结合的例子网上比较多,这里不进行赘述,并且多线程并不是真正的并行,进程才是,这里只讨论多进程。),我们将其交个子进程(下面称为子进程a)来执行。

接下来,我们只想要让按钮执行一次(只启动一个子进程a,否则会浪费系统资源甚至报错),等待子进程a执行结束时才允许再次启动,因此我们需要在子进程a执行时将按钮禁用掉,并在子进程a执行结束后使之恢复正常。禁用按钮非常好实现,在子进程a启动前设置按钮状态为 disabled,但是恢复正常比较麻烦。

  1. 如果我们在子进程a中修改按钮状态将会报错,因为子进程a和 Tk 界面属于两个进程,一个进程不能改变另一个进程的变量;
  2. 如果我们在 Tk 界面进程中循环判断子进程a是否存活又会造成进程阻塞,导致界面无法操作。

解决方案

上面提到的两个问题其中第一个属于系统规则,我们无法解决,那我们就对第二个下手。

上面也提到了,进程阻塞是因为主进程在完成一些比较费时的功能,此时无法响应其他操作,所以第二个问题我们将其交给其他进程或线程完成。

但是如果交给的是子进程(下面称为子进程b),子进程b确实可以判断子进程a是否存活,并且也不会阻塞 Tk 界面进程的执行,但是子进程b依旧不能更改 Tk 界面进程中按钮的状态。

我们将循环判断任务交给子线程时,因为该子线程与 Tk 界面同属一个进程,当子线程判断到子进程a执行结束后,即可直接修改 Tk 界面中的控件状态,并且子线程的执行也不会阻塞 Tk 界面进程(只要不设置t.join()即可)。

你可能感兴趣的:(Python,经验分享,python,tkinter,多进程,多线程)