views:

91

answers:

3

I'm attempting to build a very simple wxPython GUI that monitors and displays external data. There is a button that turns the monitoring on/off. When monitoring is turned on, the GUI updates a couple of wx StaticLabels with real-time data. When monitoring is turned off, the GUI idles.

The way I tried to build it was with a fairly simple Python Thread layout. When the 'Start Monitoring' button is clicked, the program spawns a thread that updates the labels with real-time information. When the 'Stop Monitoring' button is clicked, thread.join() is called, and it should stop.

The start function works and the real-time data updating works great, but when I click 'Stop', the whole program freezes. I'm running this on Windows 7 64-bit, so I get the usual "This Program has Stopped Responding" Windows dialog.

Here is the relevant code:

class MonGUI(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        ...
        ... other code for the GUI here ...
        ...
        # Create the thread that will update the VFO information
        self.monThread = Thread(None, target=self.monThreadWork)
        self.monThread.daemon = True
        self.runThread = False

    def monThreadWork(self):
        while self.runThread:
            ...
            ... Update the StaticLabels with info
            ... (This part working)
            ...

    # Turn monitoring on/off when the button is pressed.
    def OnClick(self, event):
        if self.isMonitoring:
            self.button.SetLabel("Start Monitoring")
            self.isMonitoring = False
            self.runThread = False
            self.monThread.join()
        else:
            self.button.SetLabel("Stop Monitoring")
            self.isMonitoring = True

            # Start the monitor thread!
            self.runThread = True
            self.monThread.start()

I'm sure there is a better way to do this, but I'm fairly new to GUI programming and Python threads, and this was the first thing I came up with.

So, why does clicking the button to stop the thread make the whole thing freeze up?

+1  A: 

It's likely hanging on join([timeout]), which blocks the calling thread until the thread whose join() method is called terminates – either normally or through an unhandled exception – or until the optional timeout occurs.

Do you have some inner loop in your thread, or a blocking call that waits for some source of data that may never come? When I wrote a basic serial program that grabbed COM port data, it would sometimes hang because a read function in my thread would block until it got something.

I would sprinkle in a few debugging print statements to see whats happening.

Edit:

I'd also use a threading.Event() instead of a Boolean flag, e.g.:

# in the init code...
self.runThread = threading.Event()

# when starting thread...
self.runThread.set()
self.monThread.start()

# in the thread...
while self.runThread.isSet():
    pass # do stuff

# killing the thread...
self.runThread.clear()
self.monThread.join()

This shouldn't make it work differently, but it's a slightly safer way to do it.

Nick T
also, according to this answer http://stackoverflow.com/questions/2822677/python-terminated-thread-cannot-restart you can't restart the same thread, you would need to recreate it. Which may not be your problem. Additionally, you should use wx events when modifying wx controls from outside the apps mainloop see http://wiki.wxpython.org/Non-Blocking%20Gui
iondiode
The multiple fixes in this answer, as well as in your comment, iondiode, did the trick. Thanks so much for the great explanation, code example, and links to previous questions!
HokieTux
+2  A: 

In wxPython, GUI operations need to take place in the main thread. At places in your code you are calling the GUI from a different thread.

The easiest solution is to use wx.CallAfter(). A line of code would look like

wx.CallAfter(self.button.SetLabel, “Start Monitoring”)

which will then call self.button.SetLabel(“Start Monitoring”) from the main thread after the function completes.

There are other ways around this as well, such as using a Python threading Queue or wx.PostEvent, but start with CallAfter because it's easiest.

Other issues are also relevant, like you can't restart the same thread, but using CallAfter will stop the crashing.

tom10
While this wasn't the exact problem I was going after, it was definitely an ancillary problem. Thanks so much for the helpful answer =)
HokieTux
+1  A: 

tom10 has the right idea with avoiding UI updates from the monitor thread.

Also, it is probably not a good idea to have the blocking call self.monThread.join() in your UI thread. If you want the UI to give some feedback that the monitor thread has actually ended, have monThreadWorker issue a wx.CallAfter() or wx.PostEvent() just before it closes.

Avoid anything that blocks in your UI thread, and you will avoid deadlocking the UI

Dan Menes