1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link rel="stylesheet" href="PythonCard.css" type="text/css" />
<title>Increasing Usefulness with Timers and Threads</title>
</head>
<body>
<div id="banner">
<h1>Timers and Threads</h1>
</div>
<?php include "sidebar.php" ?>
<div id="content">
<h2>Increasing Usefulness:Talking to program back-ends with Timers and
Threads</h2>
<h4>by David McNab</h4>
<p>This walkthrough supplements the excellent existing PythonCard 'Getting
Started' <a href="documentation.html">walkthroughs</a> by Dan Shafer and
David Primmer, and follows on from the <a href="walkthrough3.html">How to
Add a child window</a> lesson. It's based on techniques taken from the
various PythonCard <a href="/samples/samples.html">sample programs</a>.</p>
<hr />
<h3>Overview, Scope and Purpose</h3>
<p><em>This walkthrough is targeted at PythonCard Version 0.7. As PythonCard
grows, some of this walkthrough may go out of date, even fail - if this
happens, please contact <strong>david at rebirthing dot co dot nz</strong>,
and I'll update it.</em></p>
<p>The purpose of this walkthrough is to empower you to make your PythonCard
programs capable of much more meaningful work, by acquainting you with two
mechanisms - timers and threads, which can be used for communication
between your programs' graphical front-ends and back-ends.</p>
<p>Most of the top-level code you add to the front ends - your PythonCard
user interfaces - is <strong>event handlers</strong>. As with programming
with any GUI, event handlers should always complete their job fast, and
return promptly, so they don't 'clog up the works'. PythonCard is no
exception.</p>
<p>But there will be many cases where you need some real-time functionality -
back-end code which runs autonomously of user interface events.</p>
<p>An example of this is programs which communicate in real-time on the
Internet, or need to interact in real time with other software on your system
(eg monitoring system load).</p>
<p>Timers and Threads are two good mechanisms that allow you to separate your
program into:</p>
<ul>
<li>Front-End - logic which processes user interaction events</li>
<li>Back-End - logic which manages non-user-interface aspects of your
program, talks to other programs on your system or across the internet,
and possibly communicates relevant updates to the user interface.</li>
</ul>
<p>By using timers and/or threads, you can guarantee that your PythonCard
event handlers will terminate promptly, and that your user interface can
communicate as needed with your back-end logic.</p>
<h3>Timers</h3>
<p>You can set up your window class so that an event handler gets triggered
at regular intervals - the 'tick of the clock'.</p>
<p>This is useful for things like a time display on your window, or polling
for some external event (for instance, incoming mail), and dozens of other
situations.</p>
<p>Let us now add a timer to the example code you've been writing. This timer
will automatically add 10 to the number in the counter field, doing this
every 5 seconds.</p>
<p>Firstly, you will need to add an <span class="code">on_openBackground
</span> event handler to your main window. You may have already done this,
while experimenting in your learning process during the earlier walkthroughs.
But if you haven't yet done so, add the following method code to your
<span class="code">Counter</span> class:</p>
<p class="code">
def on_openBackground(self, event):<br />
print "Window opened"</p>
<p>Not very significant - but do save your file, and run <span class="code">
counter.py</span>. You'll see a message on stdout when the window opens.</p>
<p>So far, so good. Nice to know that we can receive an event when the window
gets opened. But not very useful yet.</p>
<p>Now, we need to use this <span class="code">openBackground</span> event
handler as an opportunity to set up a timer.</p>
<p>So change the event handler to the following:</p>
<p class="code">
def on_openBackground(self, event):<br />
self.myTimer = wx.wxTimer(self.components.field1,
-1) # create a timer<br />
self.myTimer.Start(5000) # launch timer, to fire
every 5000ms (5 seconds)</p>
<p>Here, we're making recourse to the wxPython classes underlying PythonCard.
We need to do this because PythonCard doesn't yet have its own timer wrappers.
You'll also notice from the <span class="code">self.components.field1</span>
that the timer is being created in respect of the <span class="code">field1</span>
widget. More on this later.</p>
<p>Don't try to run this program yet - it will barf since <span class="code">wx</span>
is an unknown symbol - we need to grab it into our namespace. To do this, add
to the top of your <span class="code">counter.py</span> program the
following:</p>
<p class="code">from wxPython import wx</p>
<p>so that <span class="code">wx</span> is a known symbol.</p>
<p>Now, we have to make sure we can receive an event every time the clock
'ticks'.</p>
<p>You'll see in the <span class="code">on_openBackground</span> event
handler above that we've linked the timer to <span class="code">field1</span>
While conceptually the timer applies to the window as a whole, there's a
weird quirk in <span class="code">wx</span> which requires timers to be
associated with specific window widgets. So we'll just appease
<span class="code">wx</span> and get on with the job.</p>
<p>To receive the clock tick events, we only have to add another handler.</p>
<p>As per the event handler naming convention, (where widgets' handlers
are called <span class="code">on_componentName_event</span>, we'll call this
handler <span class="code">on_field1_timer</span>, since timer events get
directed to the widget <span class="code">field1</span>, and the event is
called <span class="code">timer</span>.</p>
<p>Now, add the following method code into class <span class="code">
Counter</span>:</p>
<p class="code">
def on_field1_timer(self, event):<br />
print "Got a timer event"<br />
startValue = int(self.components.field1.text)<br />
endValue = startValue + 10<br />
print "Got a timer event, value is now %d" % endValue<br />
self.components.field1.text = str(endValue)<br />
# uncomment the line below if you've already followed the 'child window' walkthrough<br />
#self.minimalWindow.components.field1.text = str(endValue)</p>
<p><em>Note</em> - this is ugly, because there's a lot of duplicated
functionality. We'll leave it to you to factorise your code appropriately,
creating a generic increment method which accepts an optional amount argument
(default 1). But if you're impatient, don't worry about any factorisation,
just use the above code and all will be ok</p>
<p>Now, save your <span class="code">counter.py</span> program and run it.
You should see the number increasing by 10, every 5 seconds. I don't think I
need to say any more here - you've got the basic structure - the rest is now
up to your imagination.</p>
<p>You could use timer events to poll for external conditions, but this can
get real ugly real fast. So in the next section, we'll explore a nicer and
more general way to tie your front end code to back end functionality, using
threads.</p>
<h3>Threads</h3>
<p>Python is beautiful in its support of threads - the ability to split up
code into multiple <strong>threads of control</strong>, just like an
operating system does when it gives lots of separate programs a share of the
CPU.</p>
<p>You can ignore the objections of " Python prudes" who insist
that threads are not good programming practice. Sure, threads have their
pitfalls, such as deadlocks, race conditions etc, but if you use a bit of
common sense, and design intelligently, you can avoid these pitfalls. Also,
programming to get around the need for threads can pervert your program
logic, kind of like pushing your head down through your body and out your
back orifice. Not always a pretty sight :p</p>
<p><em>Note</em> - one actual risk of threading in Python is a syndrome
called <strong>Global Interpreter Lock</strong> or <strong>GIL</strong> for
short. GIL can strike in strange places, and cause one or more threads in
your program to freeze up for no apparent reason. If you ever have reason to
suspect GIL is occurring, simply sprinkle a few print statements in each of
your threads until you either calm your suspicions, or nail the culprit. For
example, I suffered a GIL once because a thread was building regular
expression objects with <span class="code"> re.compile()</span>. I fixed
this by building the <span class="code">re</span> objects in advance, in the
main thread. I suspect this is a Python bug (I'm using 2.2), but that's
another topic.</p>
<p>What we'll be doing here is adding a background thread to your counter
program, which (surprise, surprise) writes values (in this case, counting
from 0 in steps of 20) to your counter value.</p>
<p>The first thing you could do is disable the timer you set up in the
previous section, by commenting out the <span class="code">self.myTimer.Start(5000)</span>
statement in your <span class="code">on_openBackground</span> handler (see
above). This will avoid confusion for now, since there won't be a running
timer to complicate things.</p>
<p>Now, add to the top of <span class="code">counter.py</span> the following
statement:</p>
<p class="code">import thread, Queue</p>
<p>This will give us access to Python's thread creation/dispatch functions,
as well as message queues</p>
<h4>A Little Theory</h4>
<p>I'll keep this short and sweet. Simply, the safest way for threads to
communicate with each other is via some kind of synchronised objects. We'll
use Python's standard message queues (standard Python module <span class="code">Queue</span>
), since it's easy and safe and well supported within Python. When your
thread wants to send an event to your user interface code, it will send a
message to it, then 'wake up' your user interface so that it receives an
<strong>idle</strong> event. The <strong>idle</strong> event will check the
message queue, and react accordingly.</p>
<p><em>Note</em> - when your window classes have an <span class="code">
idle</span> event handler, this handler can get triggered by all sorts of
things, particularly when your user interface falls idle - mouse stops moving,
button click is finished etc. Within the idle event handler, we need to
check our message queue so we know <strong>when</strong> we need to react to
something in the back end.</p>
<p><em>Hint</em> - run any PythonCard program with a '<strong>-m</strong>'
argument. You'll see the program come up with a 'Message Watcher' window.
Unclick the <strong>Ignore Unused</strong> checkbox. Interact with your
program with the mouse, and you'll see a flood of events being generated.
This is a great way of "cheating" to find out what name you'll need
to give your event handlers. Another cheat is to run the widgets sample
program, which allows you to generate HTML documentation for the various
PythonCard widgets.</p>
<h4>Threads Walkthrough - Summary of Steps Involved:</h4>
<ol>
<li>Add a message queue to our <span class="code">Counter</span> class.</li>
<li>Add your thread code to <span class="code">counter.py</span>, but as a
global function, not a class method. This thread will periodically sends
messages to our window</li>
<li>In your <span class="code">on_openBackground</span> handler, launch
your thread and pass it a handle to the message queue.</li>
<li>Add an <strong>idle</strong> event handler, which picks up these
messages</li>
<li>Within the idle event handler method, check the message queue and react
accordingly</li>
</ol>
<h4>1. Add the message queue</h4>
<p>Refer back to the <span class="code">on_openBackground(self, event)</span>
handler above, and add the following statement:</p>
<p class="code">
# Add a message queue<br />
self.msgQueue = Queue.Queue()</p>
<p>This sticks a message queue into our <span class="code">Counter</span>
window class, that will be used for communication from the thread backend to
the foreground window class.</p>
<h4>2. Add a Thread Function</h4>
<p>Add the following <strong>global</strong> function to your
<span class="code">counter.py</span>:</p>
<p class="code">
def myThread(*argtuple):<br />
"""<br />
A little thread we've added<br />
"""<br />
print "myThread: entered"<br />
q = argtuple[0]<br />
print "myThread: starting loop"<br />
x = 10<br />
while 1:<br />
time.sleep(10) # time unit is seconds<br />
print "myThread x=%d" % x<br />
q.put(str(x)) # stick something on message queue<br />
wx.wxWakeUpIdle() # triggers 'idle' handlers<br />
x += 10</p>
<h4>3. Launch the Thread</h4>
<p>Add the following lines at the end of your <span class="code">on_openBackground</span>
handler:</p>
<p class="code">
# Now launch the thread<br />
thread.start_new_thread(myThread, (self.msgQueue,))</p>
<p>Notice that we have to pass the queue object to the thread in a tuple -
refer to the Python Library Reference doco for module </p>
<h4>4. Add an idle event handler</h4>
<p>In the thread function above, the operative line is <span class="code">wx.wxWakeUpIdle()</span>
. Upon calling that method, wxWindows tells all open windows, Hey, wake up -
something's happened you might need to react to! So we need to add a handler
to our Counter class to handle idle events, and thus get triggered when we
get 'woken up'. So add the following method into your <span class="code">Counter</span>
class</p>
<p class="code">
def on_idle(self, event):<br />
print "on_idle entered" </p>
<h4>5. Check the message queue and react accordingly</h4>
<p>Presently, our 'idle' handler isn't very useful - but if you run
counter.py, you'll see it gets triggered every time the program falls idle.
So now, we'll make it do what it needs to do - reacting to events from our
background thread. Replace the idle handler above with the following:</p>
<p class="code">
def on_idle(self, event):<br />
print "on_idle entered"<br />
while not self.msgQueue.empty():<br />
# Handle messages from our thread<br />
msg = self.msgQueue.get()<br />
print "on_idle: msg='%s'" % msg<br />
self.components.field1.text = msg<br />
# uncomment the following if you've followed the 'child window' walkthrough<br />
#self.minimalWindow.components.field1.text = msg</p>
<p>Now, we're all done. Launch your counter.py, and watch as the background
thread launches, and periodically sends its messages to the user interface,
which displays these on the window.</p>
<h3>Conclusion</h3>
<p>During this walkthrough, you have explored timers and threads, two easy
and powerful ways to interface your user interface classes with the back-end
of a program (where the 'real work' happens)</p>
<p>Having got a handle on front-end/back-end interactions, via threads and
timers, you are now empowered to add some serious functionality to your
PythonCard programs.</p>
<p>There is now nothing stopping you from writing any kind of application in
PythonCard.</p>
<p>A common situation in programming is where you want a program to be always
running (as a Unix daemon or a Windows NT/2k/XP 'service'), but you don't
always want its window showing. A typical approach is:</p>
<ul>
<li>Create the back-end code as a standalone 'daemon' program (or Windows
NT/2k/XP 'service'), which talks via socket connection to the front end.</li>
<li>Create the front-end code, which operates the user interface</li>
<li>Put a thread into your front end code which does the socket
communication to the daemon, and relays commands/responses/status info
between the daemon and the user interface.</li>
</ul>
<p>With this approach, you can launch and terminate the user interface
program as you like, without disrupting the backend in any way</p>
<p>So, it's over to you now. Play around with your walkthrough programs and
the PythonCard sample programs, raid the <a href="http://www.vex.net/parnassus/">Vaults of Parnassus</a>
for useful bits of code, and hack to your heart's content.</p>
<p>The only limit is your imagination and (rapidly growing) level of Python
skill.</p>
<p>Happy programming!</p>
<p>Copyright (c) 2003 by David McNab, david at rebirthing dot co dot nz.
Please feel free to mirror, copy, translate, upgrade and restribute this page,
as long as you keep up to date with Python and PythonCard, and credit the
original author.</p>
<?php include "footer.php" ?>
<p>$Revision: 1.2 $ : $Author: kasplat $ : Last updated $Date: 2004/07/26 15:35:32 $</p>
</div> <!-- end of content -->
</body>
</html>
|