Running MainLoop In a Separate Thread

Introduction

The MainLoop is usually the main thread of the application. However, there are rare situations where one might want to run the wx application on a separate thread. There are several methods within wxPython that check to see that the MainLoop is being run in the main thread. When it is not, an error may occur.

Example

Toggle line numbers
   1 import wx
   2 import numpy
   3 import threading
   4 import time
   5 
   6 EVT_NEW_IMAGE = wx.PyEventBinder(wx.NewEventType(), 0)
   7 
   8 class ImageWindow(wx.Window):
   9     def __init__(self, parent, id=-1, style=wx.FULL_REPAINT_ON_RESIZE):
  10         wx.Window.__init__(self, parent, id, style=style)
  11 
  12         self.timer = wx.Timer
  13 
  14         self.img = wx.EmptyImage(2,2)
  15         self.bmp = self.img.ConvertToBitmap()
  16         self.clientSize = self.GetClientSize()
  17 
  18         self.Bind(wx.EVT_PAINT, self.OnPaint)
  19 
  20         #For video support
  21         #------------------------------------------------------------
  22         self.Bind(EVT_NEW_IMAGE, self.OnNewImage)
  23         self.eventLock = None
  24         self.pause = False
  25         #------------------------------------------------------------
  26         
  27     def OnPaint(self, event):
  28         size = self.GetClientSize()
  29         if (size == self.clientSize):
  30             self.PaintBuffer()
  31         else:
  32             self.InitBuffer()
  33 
  34     def PaintBuffer(self):
  35         dc = wx.PaintDC(self)
  36         self.Draw(dc)
  37 
  38     def InitBuffer(self):
  39         self.clientSize = self.GetClientSize()
  40         self.bmp = self.img.Scale(self.clientSize[0], self.clientSize[1]).ConvertToBitmap()
  41         dc = wx.ClientDC(self)
  42         self.Draw(dc)
  43 
  44     def Draw(self,dc):
  45         dc.DrawBitmap(self.bmp,0,0)    
  46 
  47     def UpdateImage(self, img):
  48         self.img = img
  49         self.InitBuffer()
  50         
  51     #For video support
  52     #------------------------------------------------------------
  53     def OnNewImage(self, event):
  54         #print sys._getframe().f_code.co_name
  55         
  56         """Update the image from event.img. The eventLock should be
  57         locked by the method calling the event. If the stream is not
  58         on pause, the eventLock is released for calling method so that
  59         new image events may be called.
  60     
  61         The method depends on the use of thread.allocate_lock. The
  62         event must have the attributes, eventLock and oldImageLock
  63         which are the lock objects."""
  64         
  65         self.eventLock = event.eventLock
  66         
  67         if not self.pause:
  68             self.UpdateImage(event.img)
  69             self.ReleaseEventLock()
  70         if event.oldImageLock:
  71             if event.oldImageLock.locked():
  72                 event.oldImageLock.release()
  73 
  74     def ReleaseEventLock(self):
  75         if self.eventLock:
  76             if self.eventLock.locked():
  77                 self.eventLock.release()
  78     
  79     def OnPause(self):
  80         self.pause = not self.pause
  81         #print "Pause State: " + str(self.pause)
  82         if not self.pause:
  83             self.ReleaseEventLock()        
  84     #------------------------------------------------------------
  85 
  86 #For video support
  87 #----------------------------------------------------------------------
  88 class ImageEvent(wx.PyCommandEvent):
  89     def __init__(self, eventType=EVT_NEW_IMAGE.evtType[0], id=0):
  90         wx.PyCommandEvent.__init__(self, eventType, id)
  91         self.img = None
  92         self.oldImageLock = None
  93         self.eventLock = None
  94 #----------------------------------------------------------------------
  95         
  96 class ImageFrame(wx.Frame):
  97     def __init__(self, parent):
  98         wx.Frame.__init__(self, parent, -1, "Image Frame",
  99                 pos=(50,50),size=(640,480))
 100         self.window = ImageWindow(self)
 101         self.window.SetFocus()
 102 
 103 class ImageIn:
 104     """Interface for sending images to the wx application."""
 105     def __init__(self, parent):
 106         self.parent = parent
 107         self.eventLock = threading.Lock()
 108 
 109     def SetData(self, arr):
 110         #create a wx.Image from the array
 111         h,w = arr.shape[0], arr.shape[1]
 112 
 113         #Format numpy array data for use with wx Image in RGB
 114         b = arr.copy()
 115         b.shape = h, w, 1
 116         bRGB = numpy.concatenate((b,b,b), axis=2)
 117         data = bRGB.tostring()
 118         
 119         img = wx.ImageFromBuffer(width=w, height=h, dataBuffer=data)
 120 
 121         #Create the event
 122         event = ImageEvent()
 123         event.img = img
 124         event.eventLock = self.eventLock
 125 
 126         #Trigger the event when app releases the eventLock
 127         event.eventLock.acquire() #wait until the event lock is released
 128         self.parent.AddPendingEvent(event)
 129 
 130 class videoThread(threading.Thread):
 131     """Run the MainLoop as a thread. Access the frame with self.frame."""
 132     def __init__(self, autoStart=True):
 133         threading.Thread.__init__(self)
 134         self.setDaemon(1)
 135         self.start_orig = self.start
 136         self.start = self.start_local
 137         self.frame = None #to be defined in self.run
 138         self.lock = threading.Lock()
 139         self.lock.acquire() #lock until variables are set
 140         if autoStart:
 141             self.start() #automatically start thread on init
 142     def run(self):
 143         app = wx.PySimpleApp()
 144         frame = ImageFrame(None)
 145         frame.SetSize((800, 600))
 146         frame.Show(True)
 147     
 148         #define frame and release lock
 149         #The lock is used to make sure that SetData is defined.
 150         self.frame = frame
 151         self.lock.release()
 152     
 153         app.MainLoop()
 154         
 155     def start_local(self):
 156         self.start_orig()
 157         #After thread has started, wait until the lock is released
 158         #before returning so that functions get defined.
 159         self.lock.acquire()
 160 
 161 def runVideoThread():
 162     """MainLoop run as a thread. SetData function is returned."""
 163     
 164     vt = videoThread() #run wx MainLoop as thread
 165     frame = vt.frame #access to wx Frame
 166     myImageIn = ImageIn(frame.window) #data interface for image updates 
 167     return myImageIn.SetData
 168         
 169 def runVideo(vidSeq):
 170     """The video sequence function, vidSeq, is run on a separate
 171     thread to update the GUI. The vidSeq should have one argument for
 172     SetData."""
 173     
 174     app = wx.PySimpleApp()
 175     frame = ImageFrame(None)
 176     frame.SetSize((800, 600))
 177     frame.Show(True)
 178     
 179     myImageIn = ImageIn(frame.window)
 180     t = threading.Thread(target=vidSeq, args=(myImageIn.SetData,))
 181     t.setDaemon(1)
 182     t.start()
 183 
 184     app.MainLoop()    
 185 
 186 def runVideoAsThread():
 187     """THIS FUNCTION WILL FAIL IF WX CHECKS TO SEE THAT IT IS RUN ON
 188     MAIN THREAD.  This runs the MainLoop in its own thread and returns
 189     a function SetData that allows write access to the databuffer."""
 190     
 191     app = wx.PySimpleApp()
 192     frame = ImageFrame(None)
 193     frame.SetSize((800, 600))
 194     frame.Show(True)
 195     
 196     myImageIn = ImageIn(frame.window)    
 197 
 198     t = threading.Thread(target=app.MainLoop)        
 199     t.setDaemon(1)
 200     t.start()
 201 
 202     return myImageIn.SetData
 203 
 204 def vidSeq(SetData,loop=0):
 205     """This is a simple test of the video interface. A 16x16 image is
 206     created with a sweep of white pixels across each row."""
 207 
 208     w,h = 16,16
 209     arr = numpy.zeros((h,w), dtype=numpy.uint8)
 210     i = 0
 211     m = 0
 212     while m < loop or loop==0:
 213         print i
 214         arr[i/h,i%w] = 255
 215         h,w = arr.shape
 216         SetData(arr)
 217         time.sleep(0.1)
 218         i += 1
 219         if not (i < w*h):
 220             arr = numpy.zeros((h,w), dtype=numpy.uint8)
 221             i = 0
 222             m += 1
 223 
 224 if __name__ == '__main__':
 225         
 226     if 1:
 227         #Method 1: 
 228         runVideo(vidSeq)
 229 
 230     if 0:
 231         #Method 2:
 232         SetData = runVideoAsThread()
 233         vidSeq(SetData, loop=1)
 234 
 235     if 0:
 236         #Method 3:
 237         SetData = runVideoThread()
 238         vidSeq(SetData, loop=1)

Comments

Put your comments here.

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.