Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources] - نسخه متنی

David Ascher, Alex Martelli, Anna Ravenscroft

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید


Recipe 11.9. Combining GUIs and Asynchronous I/Owith Threads


Credit: Jacob Hallén, Laura Creighton, Boudewijn
Rempt


Problem


You need to access sockets, serial ports,
or other asynchronous (but blocking) I/O sources, while running a
GUI.


Solution


The solution
is to handle a GUI interface on one thread and communicate to it (via
Queue instances) the events on I/O channels
handled by other threads. Here's the code for the
standard Tkinter GUI toolkit that comes with Python:

import Tkinter, time, threading, random, Queue
class GuiPart(object):
def _ _init_ _(self, master, queue, endCommand):
self.queue = queue
# Set up the GUI
Tkinter.Button(master, text='Done', command=endCommand).pack( )
# Add more GUI stuff here depending on your specific needs
def processIncoming(self):
"" Handle all messages currently in the queue, if any. ""
while self.queue.qsize( ):
try:
msg = self.queue.get(0)
# Check contents of message and do whatever is needed. As a
# simple example, let's print it (in real life, you would
# suitably update the GUI's display in a richer fashion).
print msg
except Queue.Empty:
# just on general principles, although we don't expect this
# branch to be taken in this case, ignore this exception!
pass
class ThreadedClient(object):
""
Launch the "main" part of the GUI and the worker thread.
periodicCall and
endApplication could reside in the GUI part, but putting them here
means that you have all the thread controls in a single place.
""
def _ _init_ _(self, master):
""
Start the GUI and the asynchronous threads. We are in the "main"
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
""
self.master = master
# Create the queue
self.queue = Queue.Queue( )
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.endApplication)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
self.running = True
self.thread1 = threading.Thread(target=self.workerThread1)
self.thread1.start( )
# Start the periodic call in the GUI to check the queue
self.periodicCall( )
def periodicCall(self):
"" Check every 200 ms if there is something new in the queue. ""
self.master.after(200, self.periodicCall)
self.gui.processIncoming( )
if not self.running:
# This is the brutal stop of the system. You may want to do
# some cleanup before actually shutting it down.
import sys
sys.exit(1)
def workerThread1(self):
""
This is where we handle the asynchronous I/O. For example, it may be
a 'select( )'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
""
while self.running:
# To simulate asynchronous I/O, create a random number at random
# intervals. Replace the following two lines with the real thing.
time.sleep(rand.random( ) * 1.5)
msg = rand.random( )
self.queue.put(msg)
def endApplication(self):
self.running = False
rand = random.Random( )
root = Tkinter.Tk( )
client = ThreadedClient(root)
root.mainloop( )


Discussion


This recipe demonstrates the easiest way of handling access to
sockets, serial ports, and other asynchronous I/O ports while running
a Tkinter-based GUI. The recipe's principles
generalize to other GUI toolkits, since most toolkits make it
preferable to access the GUI itself from a single thread, and all
offer a toolkit-dependent way to set up periodic polling as this
recipe does.

Tkinter, like most other GUIs, is best used with all graphic commands
in a single thread. On the other hand, it's far more
efficient to make I/O channels block, then wait for something to
happen, rather than using nonblocking I/O and having to poll at
regular intervals. The latter approach may not even be available in
some cases, since not all data sources support nonblocking I/O.
Therefore, for generality as well as for efficiency, we should handle
I/O with a separate thread, or more than one. The I/O threads can
communicate in a safe way with the
"main", GUI-handling thread through
one or more Queues. In this recipe, the GUI thread
still has to do some polling (on the Queues), to
check whether something in the Queue needs to be
processed. Other architectures are possible, but they are much more
complex than the one in this recipe. My advice is to start with this
recipe, which will handle your needs over 90% of the time, and
explore the much more complex alternatives only if it turns out that
this approach cannot meet your performance requirements.

This recipe lets a worker thread block in a select
(simulated by random sleeps in the recipe's example
worker thread). Whenever something arrives, it is received and
inserted in a Queue instance. The main (GUI)
thread polls the Queue five times per second and
processes all messages that have arrived since it last checked.
(Polling 5 times per second is frequent enough that the end user will
not notice any significant delay but infrequent enough that the
computational load on the computer will be negligible.) You may want
to fine-tune this feature, depending on your needs.

This recipe solves a common problem that is frequently asked about on
Python mailing lists and newsgroups. Other solutions, involving
synchronization between threads, help you solve such problems without
polling (the self.master.after call in the
recipe). Unfortunately, such solutions are generally complicated and
messy, since you tend to raise and wait for semaphores throughout
your code. In any case, a GUI already has several polling mechanisms
built into it (the "main" event
loop), so adding one more won't make much
difference, especially since it seldom runs. The code has been tested
in depth only under Linux, but it should work on any platform with
working threads, including Windows.

Here is a PyQt equivalent, with very minor variations:

import sys, time, threading, random, Queue, qt
class GuiPart(qt.QMainWindow):
def _ _init_ _(self, queue, endcommand, *args):
qt.QMainWindow._ _init_ _(self, *args)
self.queue = queue
# We show the result of the thread in the gui, instead of the console
self.editor = qt.QMultiLineEdit(self)
self.setCentralWidget(self.editor)
self.endcommand = endcommand
def closeEvent(self, ev):
"" We just call the endcommand when the window is closed,
instead of presenting a button for that purpose. ""
self.endcommand( )
def processIncoming(self):
"" Handle all the messages currently in the queue (if any). ""
while self.queue.qsize( ):
try:
msg = self.queue.get(0)
self.editor.insertLine(str(msg))
except Queue.Empty:
pass
class ThreadedClient(object):
""
Launch the "main" part of the GUI and the worker thread.
periodicCall and
endApplication could reside in the GUI part, but putting them here
means that you have all the thread controls in a single place.
""
def _ _init_ _(self):
# Create the queue
self.queue = Queue.Queue( )
# Set up the GUI part
self.gui = GuiPart(self.queue, self.endApplication)
self.gui.show( )
# A timer to periodically call periodicCall
self.timer = qt.QTimer( )
qt.QObject.connect(self.timer, qt.SIGNAL("timeout( )"),
self.periodicCall)
# Start the timer -- this replaces the initial call to periodicCall
self.timer.start(200)
# Set up the thread to do asynchronous I/O
# More can be made if necessary
self.running = True
self.thread1 = threading.Thread(target=self.workerThread1)
self.thread1.start( )
def periodicCall(self):
""
Check every 200 ms if there is something new in the queue.
""
self.gui.processIncoming( )
if not self.running:
root.quit( )
def endApplication(self):
self.running = False
def workerThread1(self):
""
This is where we handle the asynchronous I/O. For example, it may be
a 'select( )'. An important thing to remember is that the thread has
to yield control once in a while.
""
while self.running:
# To simulate asynchronous I/O, we create a random number at
# random intervals. Replace the following 2 lines with the real
# thing.
time.sleep(rand.random( ) * 0.3)
msg = rand.random( )
self.queue.put(msg)
rand = random.Random( )
root = qt.QApplication(sys.argv)
client = ThreadedClient( )
root.exec_loop( )

As you can see, this PyQt variation has
a structure that's uncannily similar to the Tkinter
version, with just a few variations (and a few enhancements, such as
using QApplication.quit instead of the more brutal
sys.exit, and displaying the
thread's result in the GUI itself rather than on the
console).


See Also


Documentation of the standard library modules
threading and Queue in the
Library Reference and Python in a
Nutshell
; information about Tkinter can be obtained from
a variety of sources, such as Fredrik Lundh, An
Introduction to Tkinter
(Pythonware: http://www.pythonware.com/library), New
Mexico Tech's Tkinter
Reference
(http://www.nmt.edu/tcc/help/lang/python/docsl),
Python in a Nutshell, and various other books;
information about PyQt can be found at PyQt's own
web site, http://www.riverbankcomputing.co.uk/pyqt/index.php.

/ 394