问题描述:

I've spent the past few days reading various threads about making tkinter thread-safe and running children without blocking the main thread. I thought I had arrived at a solution that allowed my code to run as I wanted it to, but now my main thread becomes non-responsive when my child process finishes. I can move the window around but the GUI part shows a loading cursor, whites out, and says "Not Responding" in the title of the window. I can let it sit like that forever and nothing will happen. I know what part of the code is causing the problem but I am not sure why it's causing the GUI to freeze. I'm using Windows.

I want my GUI to run another process using multiprocess. I have sys.stdout and sys.stderr routed to a queue and I use threading to create a thread that holds an automatic queue checker that updates the GUI every 100 ms so my GUI updates in "real time". I have tried every way of sending the child's stdout/stderr to the GUI and this is the only way that works the way I want it to (except for the freezing bit), so I would like to find out why it's freezing. Or I would like help setting up a proper way of sending the child's output to the GUI. I have tried every method I could find and I could not get them to work.

My main thread:

#### _______________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()

My child process (qBMPchugger):

#### _______________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!")

It's the part under # Send outputs to the queue (starting with the output class and ending with sys.stderr = error(sys.stderr, q)) that is causing my program to freeze. Why is that holding up my main thread when the child process finishes executing? EDIT: I think the freezing is being caused by the queue remaining open when the child process closes... or something. It's not the particular snippet of code like I thought it was. It happens even when I change the print statements to q.put("text") in either the parent or the child.

What is a better way to send the output to the queue? If you link me to a topic that answers my question, PLEASE show me how to implement it within my code. I have not been successful with anything I've found so far and chances are that I've already tried that particular solution and failed.

网友答案:

Use a manager list or dictionary to communicate between processes https://docs.python.org/2/library/multiprocessing.html#sharing-state-between-processes . You can have a process update the dictionary and send it to the GUI/some code outside the processes, and vice versa. The following is a simple, and a little sloppy, example of doing it both ways.

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()
网友答案:

For my particular problem, the main thread was trying to read from the queue when the queue was empty and not having anything else put into it. I don't know the exact details as to why the main loop got hung up on that thread (self._update in my code) but changing _update to the following stopped making the GUI non-responsive when the child finished:

def _update(self):
    if q.empty():
        pass
    else:
        msg = q.get()
        self.write(msg)
        self.update_idletasks()
相关阅读:
Top