由于重定向Stdout,子进程完成后GUI未响应

时间:2015-08-11 18:07:01

标签: python user-interface tkinter multiprocessing stdout

过去几天我一直在阅读有关制作tkinter线程安全和运行子节点的各种线程,而不会阻塞主线程。我以为我已经找到了一个允许我的代码按照我的意愿运行的解决方案,但现在我的主要线程在我的子进程完成时变得没有响应。我可以移动窗口,但GUI部分显示一个加载光标,白色,并说"无响应"在窗口的标题。我可以让它像这样永远坐着,什么都不会发生。我知道代码的哪一部分导致了问题,但我不确定它导致GUI冻结的原因。我正在使用Windows。

我希望我的GUI使用ThingId运行另一个进程。我有multiprocesssys.stdout路由到队列,我使用sys.stderr创建一个线程,该线程包含一个自动队列检查器,每100 ms更新一次GUI,因此我的GUI更新为#34 ;实时"。我已经尝试过将孩子的threading发送到GUI的各种方式,这是唯一能按我想要的方式工作的方式(除了冻结位),所以我想找出来为什么它会冻结。或者我想帮助设置一种将孩子的输出发送到GUI的正确方法。我已经尝试了我能找到的每一种方法,但我无法让它们发挥作用。

我的主要话题:

stdout/stderr

我的子进程(qBMPchugger):

#### _______________IMPORT MODULES_________________###
import Tkinter
import multiprocessing
import sys
from threading import Thread
import qBMPchugger

###____________Widgets__________________###
class InputBox(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        self.initialize()

    def initialize(self):
        # Styles
        self.grid()

        # Approval
        self.OKbutton = Tkinter.Button(self, text=u"OK", command=self.OKgo, anchor="e")
        self.OKbutton.pack(side="right")

        self.view = Tkinter.Text(self)
        self.view.pack(side="left")
        self.scroll = Tkinter.Scrollbar(self, orient=Tkinter.VERTICAL)
        self.scroll.config(command=self.view.yview)
        self.view.config(yscrollcommand=self.scroll.set)
        self.scroll.pack(side="left")

    def write(self, text):
        self.view.insert("end", text)

    def OKgo(self):
        sys.stdout = self
        sys.stderr = self

        checker = Thread(target=self._update)
        checker.daemon = True
        checker.start()

        self.view.delete(1.0, "end")
        self.update_idletasks()

        print("Loading user-specified inputs...")
        path = "C:/"
        inarg = (q, path)

        print("Creating the program environment and importing modules...")
        # Starts the text monitor to read output from the child process, BMPchugger
        p = multiprocessing.Process(target=qBMPchugger.BMPcode, args=inarg)
        p.daemon = 1
        p.start()

    def _update(self):
        msg = q.get()
        self.write(msg)
        self.update_idletasks()
        self.after(100, self._update)

if __name__ == "__main__":
    app = InputBox(None)
    app.title("File Inputs and Program Settings")

    q = multiprocessing.Queue()

    app.mainloop()

它是#### _______________INITIALIZE_________________### import os import sys import tkMessageBox import Tkinter class BadInput(Exception): pass def BMPcode(q, path): # Create root for message boxes boxRoot = Tkinter.Tk() boxRoot.withdraw() # Send outputs to the queue class output: def __init__(self, name, queue): self.name = name self.queue = queue def write(self, msg): self.queue.put(msg) def flush(self): sys.__stdout__.flush() class error: def __init__(self, name, queue): self.name = name self.queue = queue def write(self, msg): self.queue.put(msg) def flush(self): sys.__stderr__.flush() sys.stdout = output(sys.stdout, q) sys.stderr = error(sys.stderr, q) print("Checking out the Spatial Analyst extension from GIS...") # Check out extension and overwrite outputs ### _________________VERIFY INPUTS________________### print("Checking validity of specified inputs...") # Check that the provided file paths are valid inputs = path for i in inputs: if os.path.exists(i): pass else: message = "\nInvalid file path: {}\nCorrect the path name and try again.\n" tkMessageBox.showerror("Invalid Path", message.format(i)) print message.format(i) raise BadInput print("Success!") 下的部分(从输出类开始,以# Send outputs to the queue结尾)导致我的程序冻结。为什么在子进程完成执行时会占用我的主线程? 编辑:我认为冻结是由于子进程关闭时队列保持打开...或其他原因造成的。它不像我想象的那样特定的代码片段。即使我在父母或孩子中将打印陈述更改为sys.stderr = error(sys.stderr, q),也会发生这种情况。

将输出发送到队列的更好方法是什么?如果您将我链接到一个回答我问题的主题,请告诉我如何在我的代码中实现它。我迄今为止找不到的任何东西都没有成功,很有可能我已经尝试过那个特定的解决方案并且失败了。

2 个答案:

答案 0 :(得分:0)

使用经理列表或字典在流程https://docs.python.org/2/library/multiprocessing.html#sharing-state-between-processes之间进行通信。您可以让一个进程更新字典并将其发送到GUI /进程外的一些代码,反之亦然。以下是两种方式的简单,有点草率的例子。

import time
from multiprocessing import Process, Manager

def test_f(test_d):
   """  frist process to run
        exit this process when dictionary's 'QUIT' == True
   """
   test_d['2'] = 2     ## add as a test
   while not test_d["QUIT"]:
      print "P1 test_f", test_d["QUIT"]
      test_d["ctr"] += 1
      time.sleep(1.0)

def test_f2(test_d):
    """ second process to run.  Runs until the for loop exits
   """
    for j in range(0, 10):
       ## print to show that changes made anywhere
       ## to the dictionary are seen by this process
       print "     P2", j, test_d
       time.sleep(0.5)

    print "second process finished"

if __name__ == '__main__':
   ##--- create a dictionary via Manager
   manager = Manager()
   test_d = manager.dict()
   test_d["ctr"] = 0
   test_d["QUIT"] = False

   ##---  start first process and send dictionary
   p = Process(target=test_f, args=(test_d,))
   p.start()

   ##--- start second process
   p2 = Process(target=test_f2, args=(test_d,))
   p2.start()

   ##--- sleep 2 seconds and then change dictionary
   ##     to exit first process
   time.sleep(2.0)
   print "\nterminate first process"
   test_d["QUIT"] = True
   print "test_d changed"
   print "dictionary updated by processes", test_d

   ##---  may not be necessary, but I always terminate to be sure
   time.sleep(5.0)
   p.terminate()
   p2.terminate()

答案 1 :(得分:0)

对于我的特殊问题,当队列为空且没有任何其他内容放入其中时,主线程试图从队列中读取。我不知道为什么主循环挂在该线程上的确切细节(我的代码中为self._update)但将_update更改为以下内容时停止使GUI无响应时完成:

def _update(self):
    if q.empty():
        pass
    else:
        msg = q.get()
        self.write(msg)
        self.update_idletasks()