⇤ ← Revision 1 as of 2008-03-03 03:53:14
Size: 8036
Comment:
|
Size: 12067
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 4: | Line 4: |
Line 6: | Line 7: |
=== The Typical Method to Run Separate Threads === If you have code that must be run on a separate thread from the wx application, you will typically start a new thread to run that code from within the MainLoop of the wx application. This keeps the GUI responsive as described in LongRunningTasks. The thread may be started from an event within the wx application or it could be started before app.MainLoop() is called. Events are used to communicate with the wx application, for example, to update a windows content. Many of the tools available in the Threading package, such as Locks, are useful in building these applications. === Starting MainLoop In a Thread === Suppose you want to write a script that starts the wx application and then sends events to the GUI in order to update a window. You could wrap up the script as a function and then pass the function to the wx application that then starts the script on its own thread. However, if you really want to interact with the GUI dynamically from your script, you may want to start the GUI on a separate thread and then send events to the window from the main script. I found that doing this was not as easy as it seems. RobinDunn describes the requirement of the main thread in an email response on the wxPython-users list: wxWindows expects that GUI operations and event dispatching can only take place in the main thread, (because of no thread safty in most GUI libs) and it takes some precautions to ensure that things work this way, and problems will probably happen eventually if the MainLoop thread is different than wxWindows "main thread." Whatever thread is the current one when wxWindows is initialized is what it will consider the "main thread." For wxPython 2.4 that happens when wxPython.wx is inported the first time. For 2.5 it will be when the wx.App object is created. End quote. When you instantiate a wx.App() with a frame and then start the main loop on a separate thread, wx may report that the main loop is not run in the main thread. In order to resolve this, we can use the threading.Tread class to instantiate the wx.App within the definition of the run method of that class. The wx application now believes that it runs in the main thread. However, in order to access the attributes of the wx application, I use a threading.Lock to block the main thread until the wx application initializes the variables within the Thread class. Otherwise, the attributes that the main script needs to communicate with the GUI may not have been initialized and an error will be given. I found that a sleep statement can resolve this but the threading.Lock is more robust. |
|
Line 7: | Line 20: |
The code below gives examples of running separate threads. The purpose of the code is to show an example of a simple image viewer. The image viewer is periodically updated with images. Method 1 allows the main loop to be run as the main thread. The function vidSeq is a video sequencer that gets called on its own thread before the main loop is started. Essentially, the vidSeq function is passed as an argument to the application that then calls it. This method works well. Method 2 is a quick attempt to run the main loop on a thread so that the vidSeq function can be called from the main script instead. This method actually works here but will give errors for a more complex example. I found that any use of wx.Timer causes this method to fail. Method 3 creates an instance of threading.Tread and the wx application is initialized within the run statement. This tread automatically starts when instantiated. The start attribute has been redefined so that a threading.Lock can be used to block the main application until the critical parts of the run statement have been reached. The critical part is where self.frame is defined. This code sits in runVideoThread. When this function is called, the main loop is started and the SetData function is returned. Now vidSeq is called from the main script. It updates the images in the viewer using the SetData command. |
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.
The Typical Method to Run Separate Threads
If you have code that must be run on a separate thread from the wx application, you will typically start a new thread to run that code from within the MainLoop of the wx application. This keeps the GUI responsive as described in LongRunningTasks. The thread may be started from an event within the wx application or it could be started before app.MainLoop() is called. Events are used to communicate with the wx application, for example, to update a windows content. Many of the tools available in the Threading package, such as Locks, are useful in building these applications.
Starting MainLoop In a Thread
Suppose you want to write a script that starts the wx application and then sends events to the GUI in order to update a window. You could wrap up the script as a function and then pass the function to the wx application that then starts the script on its own thread. However, if you really want to interact with the GUI dynamically from your script, you may want to start the GUI on a separate thread and then send events to the window from the main script. I found that doing this was not as easy as it seems.
RobinDunn describes the requirement of the main thread in an email response on the wxPython-users list: wxWindows expects that GUI operations and event dispatching can only take place in the main thread, (because of no thread safty in most GUI libs) and it takes some precautions to ensure that things work this way, and problems will probably happen eventually if the MainLoop thread is different than wxWindows "main thread." Whatever thread is the current one when wxWindows is initialized is what it will consider the "main thread." For wxPython 2.4 that happens when wxPython.wx is inported the first time. For 2.5 it will be when the wx.App object is created. End quote.
When you instantiate a wx.App() with a frame and then start the main loop on a separate thread, wx may report that the main loop is not run in the main thread. In order to resolve this, we can use the threading.Tread class to instantiate the wx.App within the definition of the run method of that class. The wx application now believes that it runs in the main thread. However, in order to access the attributes of the wx application, I use a threading.Lock to block the main thread until the wx application initializes the variables within the Thread class. Otherwise, the attributes that the main script needs to communicate with the GUI may not have been initialized and an error will be given. I found that a sleep statement can resolve this but the threading.Lock is more robust.
Example
The code below gives examples of running separate threads. The purpose of the code is to show an example of a simple image viewer. The image viewer is periodically updated with images. Method 1 allows the main loop to be run as the main thread. The function vidSeq is a video sequencer that gets called on its own thread before the main loop is started. Essentially, the vidSeq function is passed as an argument to the application that then calls it. This method works well. Method 2 is a quick attempt to run the main loop on a thread so that the vidSeq function can be called from the main script instead. This method actually works here but will give errors for a more complex example. I found that any use of wx.Timer causes this method to fail. Method 3 creates an instance of threading.Tread and the wx application is initialized within the run statement. This tread automatically starts when instantiated. The start attribute has been redefined so that a threading.Lock can be used to block the main application until the critical parts of the run statement have been reached. The critical part is where self.frame is defined. This code sits in runVideoThread. When this function is called, the main loop is started and the SetData function is returned. Now vidSeq is called from the main script. It updates the images in the viewer using the SetData command.
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.