使用 multiprocessing.Process 时 Tkinter GUI 冻结

Joh*_*hon 2 python user-interface tkinter freeze multiprocessing

你能向我解释一下,当将工作函数作为单独的进程执行时,如何防止 python GUI 冻结?

我编写了一个 python GUI,单击按钮即可通过多处理模块启动一个进程。我决定使用多处理而不是线程,因为我喜欢选择启动、暂停、恢复和终止进程。

不幸的是,当工作进程运行时,GUI 冻结并变得无响应,因此我无法按“暂停”按钮。

stackoverflow 上多次报告了 GUI 的冻结问题,但似乎没有该问题的单一来源,也没有统一的解决方案。因此我的问题不是重复的。

我目前完全不知道如何解决这个冻结问题。到目前为止,我对解决方案的唯一猜测是使用名为 mtTkinter 的 Tkinter 线程安全包装器,希望它也有助于多处理。但它什么也没做。

也许需要在 GUI 和工作进程之间添加另一层。

欢迎任何建议、提示和解决方案。

最小代码:

import Tkinter as tk
import time
import multiprocessing
import psutil


def test_function():        
    print 'Test process starting.'
    for i in range(11):
        print "Test process running, step: ", i
        time.sleep(1)
    print 'Test process finished.'


   class MainScreen(tk.Tk):

       def __init__(self):
           tk.Tk.__init__(self)
           self.title("CardZilla 0.1")
           self.resizable(0, 0)

           self.s_button = tk.Button(self, text="START", command=self.on_start)
           self.p_button = tk.Button(self, text="PAUSE", state='disabled',  command=self.on_pause)

           self.s_button.pack(side=tk.LEFT)
           self.p_button.pack(side=tk.LEFT)

       def update_all(self): #updates button states
           b_list= {self.s_button, self.p_button}
           for b in b_list:
               b.update()

       def on_start(self):
           if self.s_button["text"] == "RESUME":
               self.s_button["text"] = "START"
               self.p_button["text"] = "PAUSE"
               self.ps.resume() #resume command for process
           else:
               str = 'test arg'
               #queue = Queue()
               p = multiprocessing.Process(None, test_function)
               p.start()
               self.ps = psutil.Process(p.pid) #getting pid of process, how to move self.ps under def __init__(self): ?

               self.s_button["state"] = 'disabled'
               self.p_button["state"] = 'normal'
               self.update_all()

               p.join() #ending process

               self.s_button["state"] = 'normal'
               self.p_button["state"] = 'disabled'
               self.update_all()

       def on_pause(self):
           if self.p_button["text"] == "PAUSE":
               print 'Pause button clicked.'

               self.s_button["text"] = "RESUME"
               self.s_button["state"] = 'normal'
               self.p_button["text"] = "CANCEL"
               self.update_all()
               self.ps.suspend() #pausing command for process

           else:
               self.s_button["text"] = "START"
               self.s_button["state"] = 'normal'
               self.p_button["text"] = "PAUSE"
               self.p_button["state"] = "disabled"
               self.update_all()
               self.ps.terminate() #good to terminate via psutils versus native multiprocessing interface?


if __name__ == '__main__':
    ms = MainScreen()
    ms.mainloop()
Run Code Online (Sandbox Code Playgroud)

dan*_*ano 5

问题是您正在调用p.join()等待进程在on_start函数内退出:

           p = multiprocessing.Process(None, test_function)
           p.start()
           self.ps = psutil.Process(p.pid) #getting pid of process, how to move self.ps under def __init__(self): ?

           self.s_button["state"] = 'disabled'
           self.p_button["state"] = 'normal'
           self.update_all()

           p.join() # this will block until `p` is complete
Run Code Online (Sandbox Code Playgroud)

这样做可以防止控制权返回到事件循环,直到进程退出,这意味着在这种情况发生并on_start退出之前,GUI 将没有响应。删除对的调用join,GUI 应该会被解锁。

现在,这暴露了您的设计的其他问题,因为您只想在该过程完成后更新 GUI。您可以使用该方法偶尔检查该过程是否已完成,从而做到这一点after。这样,事件循环基本上就解除了阻塞,我们只需短暂地阻塞它以查看该过程是否已完成。如果有,我们会更新 GUI 按钮。如果没有,我们安排检查方法在几秒钟内再次运行0.5

def check_proc(self):
   if not self.p.is_alive():
       # Process is done. Update the GUI
       self.p.join()
       self.s_button["state"] = 'normal'
       self.p_button["state"] = 'disabled'
       self.update_all()
   else:
       # Not done yet. Check again later.
       self.after(500, self.check_proc)


def on_start(self):
   if self.s_button["text"] == "RESUME":
       self.s_button["text"] = "START"
       self.p_button["text"] = "PAUSE"
       self.ps.resume() #resume command for process
   else:
       str = 'test arg'
       #queue = Queue()
       self.p = multiprocessing.Process(None, test_function)
       self.p.start()
       self.ps = psutil.Process(self.p.pid)

       self.s_button["state"] = 'disabled'
       self.p_button["state"] = 'normal'
       self.update_all()
       self.after(500, self.check_proc)  # Check to see if the process is done in 0.5 seconds

       #p.join() #ending process
Run Code Online (Sandbox Code Playgroud)