Sophie

Sophie

distrib > Mandriva > 2010.0 > i586 > media > contrib-release > by-pkgid > 5f5d88c6b2988e78ee210e72f14d11ca > files > 926

python-card-0.8.1-9mdv2010.0.noarch.rpm

<!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 />
    &nbsp;&nbsp;&nbsp;&nbsp;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 />
    &nbsp;&nbsp;&nbsp;&nbsp;self.myTimer = wx.wxTimer(self.components.field1,
    -1) # create a timer<br />
    &nbsp;&nbsp;&nbsp;&nbsp;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 />
    &nbsp;&nbsp;&nbsp;&nbsp;print "Got a timer event"<br />
    &nbsp;&nbsp;&nbsp;&nbsp;startValue = int(self.components.field1.text)<br />
    &nbsp;&nbsp;&nbsp;&nbsp;endValue = startValue + 10<br />
    &nbsp;&nbsp;&nbsp;&nbsp;print "Got a timer event, value is now %d" % endValue<br />
    &nbsp;&nbsp;&nbsp;&nbsp;self.components.field1.text = str(endValue)<br />
    &nbsp;&nbsp;&nbsp;&nbsp;# uncomment the line below if you've already followed the 'child window' walkthrough<br />
    &nbsp;&nbsp;&nbsp;&nbsp;#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 &quot; Python prudes&quot; 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 &quot;cheating&quot; 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">
    &nbsp;&nbsp;&nbsp;&nbsp;# Add a message queue<br />
    &nbsp;&nbsp;&nbsp;&nbsp;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 />
    &nbsp;&nbsp;&nbsp;&nbsp;"""<br />
    &nbsp;&nbsp;&nbsp;&nbsp;A little thread we've added<br />
    &nbsp;&nbsp;&nbsp;&nbsp;"""<br />
    &nbsp;&nbsp;&nbsp;&nbsp;print "myThread: entered"<br />
    &nbsp;&nbsp;&nbsp;&nbsp;q = argtuple[0]<br />
    &nbsp;&nbsp;&nbsp;&nbsp;print "myThread: starting loop"<br />
    &nbsp;&nbsp;&nbsp;&nbsp;x = 10<br />
    &nbsp;&nbsp;&nbsp;&nbsp;while 1:<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time.sleep(10) # time unit is seconds<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "myThread x=%d" % x<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q.put(str(x)) # stick something on message queue<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wx.wxWakeUpIdle() # triggers 'idle' handlers<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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">
    &nbsp;&nbsp;&nbsp;&nbsp;# Now launch the thread<br />
    &nbsp;&nbsp;&nbsp;&nbsp;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 />
    &nbsp;&nbsp;&nbsp;&nbsp;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 />
    &nbsp;&nbsp;&nbsp;&nbsp;print "on_idle entered"<br />
    &nbsp;&nbsp;&nbsp;&nbsp;while not self.msgQueue.empty():<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Handle messages from our thread<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg = self.msgQueue.get()<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "on_idle: msg='%s'" % msg<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.components.field1.text = msg<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# uncomment the following if you've followed the 'child window' walkthrough<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#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>