1

I am working with python v2.7 and wxPython v3.0 on windows 7 OS. In the following code snippet I have implemented a publisher-subscriber mechanism to update my GUI. I am using wx.CallAfter() for sending the updates to my GUI main loop.

Problem: Why does my code snippet provided below my application crashes. Why is this happening? How to avoid this?

Code: The images used in the code can be downloaded from here blue.bmp & g.bmp.

import wx
import time
from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub
from threading import Thread
import threading
import random

class gui(wx.Frame):

    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, None, id, title, size=(200,200), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
        self.mainPanel = mainPanel = wx.Panel(self, -1)
        mySizer = wx.BoxSizer(wx.VERTICAL)
        self.myButton1 = wx.Button(mainPanel, -1, size=(30,30))
        mySizer.Add(self.myButton1)
        mainPanel.SetSizer(mySizer)
        Bimage_file = 'blue.bmp'
        self.Blue  = wx.Image(Bimage_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        Gimage_file = 'g.bmp'
        self.Green  = wx.Image(Gimage_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        pub.subscribe(self.updateImages, 'Update')

    def updateImages(self, value):
        self.myButton1.DestroyChildren()
        if value <= 5:
            wx.StaticBitmap(self.myButton1, -1, self.Blue, (0, 0))
            self.mainPanel.Layout()
        else:
            wx.StaticBitmap(self.myButton1, -1, self.Green, (0, 0))
            self.mainPanel.Layout()
class myThread(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.start()
    def run(self):
        while True:
            result = random.randrange(0,10)
            wx.CallAfter(pub.sendMessage, 'Update', value=result)
            #Uncomment the following line to avoid the crash!!!
            #time.sleep(1)
if __name__=='__main__':
    app = wx.App()
    frame = gui(parent=None, id=-1, title="Demo")
    frame.Show()
    myThread()
    app.MainLoop()

However if I add the following line after wx.CallAfter() it works fine:

time.sleep(1)

Thanks for your time!

ρss
  • 5,115
  • 8
  • 43
  • 73

2 Answers2

3

The problem is probably due to overflowing the Windows message queue. There are several tickets about this bug in wxWidgets bug tracker and some of its occurrences have been fixed, but probably not all.

In any case, even if we manage to always avoid the overflow, it's still a bad idea to flood the main thread with events like this (because every call to wx.CallAfter() is morally equivalent to an event). In real life situations, the background thread would be doing some work in between them, so this is unlikely to happen, but if you really need to make it work in such toy example, using sleep() is probably actually the right thing to do.

VZ.
  • 21,740
  • 3
  • 39
  • 42
  • Thanks. Can you please answer if this is a wxPython's or Windows OS's issue? Do you think that wx.CallLater() can help in this case or will it not make any difference? – ρss Mar 28 '14 at 13:04
  • 2
    I don't know the details of `wx.CallLater()` implementation so I'm not sure. The good news is that I think the crash has actually already been fixed, see [this ticket](http://trac.wxwidgets.org/ticket/15951), so your code should work without changes with wxPython 3.0.1 when it's released -- or if you build your own wxPython using the current 3.0 branch sources. – VZ. Mar 28 '14 at 13:32
  • 1
    @ps VZ's answer seems very plausible. Then `wx.CallLater` will not help: this function calls the given function later but still returns immediately so you will still be overflowing the event queue. You don't need to update the GUI a million times per second; a few times per second should be sufficient if you want smooth effect, so you could use `time.sleep(0.1)` for 10 times per second, or only generate the message when the updated value has changed by more than a certain value (certainly do not generate message if value to publish has not changed!). – Oliver Mar 29 '14 at 13:52
0

Perhaps your thread should have a return callback or just a variable (semaphore) to know when the CallAfter has been executed?

Wrap your pub.sendMessage call in another function that will, for example, clear a semaphore flag after sending the message. Your thread can set semaphore prior to issuing the CallAfter, then wait for the semaphore to clear before sending another.

If you make it a counter, you upcount at set, and downcount at clear, and only allow it to go so high, to limit the number queued up CallAfter's.

RufusVS
  • 4,008
  • 3
  • 29
  • 40