使用multiprocessing.Process时,Tkinter GUI会冻结

时间:2014-09-13 23:18:27

标签: python user-interface tkinter multiprocessing freeze

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

我编写了一个python GUI,只需按一下按钮,就可以通过多处理模块启动一个进程。我决定进行多处理和线程处理,因为我喜欢选择 启动,暂停,恢复和终止该过程。

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

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

我目前根本不知道如何解决这个冻结问题。 到目前为止,我对解决方案的唯一猜测是使用一个名为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()

1 个答案:

答案 0 :(得分:3)

问题是您正在调用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

执行此操作可防止控制返回到事件循环,直到进程退出,这意味着直到发生这种情况并且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