File: class/Extras/Code/pp3e/threadtools.py

File: class/Extras/Code/pp3e/threadtools.py

#########################################################
# system-wide thread interface utilities for GUIs
# single thread queue and checker shared by all windows
# never block GUI - just verify operations and quits
# worker threads can overlap with main, other workers
#
# using a queue of callback functions and argument is
# more useful than a simple data queue if there can be
# many kinds of threads running at the same time - each
# kind may have a different implied exit action
#
# because GUI API is not completely thread-safe,
# instead of calling GUI update callbacks directly
# after thread exit, place them on a shared queue,
# to be run from a timer loop in the main thread;
# may be useful even if GUI is thread safe, to make
# GUI update points less random and unpredictable;
# to use, split modal action into pre and post-thread
# parts, plus threaded action and error handler;
#
# assumes thread action raises exception on failure,
# and thread action has a 'progress' callback argument
# if it supports progress updates; also assumes a
# that queue will contain callback functions for use
# in a GUI app: needs widget object and 'after' event;
# use queue directly for other producer/consumer apps;
#
# caveat: this loop could be diasabled if no threads running,
# but system-wide CPU utilization stayed at 1% on my machine
# before and after this program was launched, and this program's
# process took 0% or 1% of the CPU itself (usually 0%, and only
# occasionally 1% while worker thread were running)- the
# overhead of thread checker loop is trivial at 250msecs,
# 4x per second;
#
# see also: threadtools-nongui.py - fails if call update
##########################################################
#????????
# caveat: as coded, progress updates only reported 10x
# per second: if update come faster than they can be taken
# off the queue, GUI will fall behind and not do exit action
# at true thread exit; queue only supports get/put, not
# changes; better alts may be to consume entire queue on
# each timer callback (but may block rest of gui if many
# actions on queue), or signal progress without putting
# items on queue for each update - cd make mail xfer loops
# smarter and only call progress every N times; this may or
# may not be a realistic concern: how many network connections
# can respond to more than 10 top or retr request per second?
# only an ussue of very may messages in mbox
# bumped up timer to 10x per sec, cpu usage at 0% usally, 1% occasionally
# run if no threads
try: # raise ImportError to
 import thread # run with gui blocking
except ImportError: # if threads not available 
 class fakeThread:
 def start_new_thread(self, func, args):
 func(*args)
 thread = fakeThread()
import Queue, sys
threadQueue = Queue.Queue(maxsize=0) # infinite size
def threadChecker(widget, delayMsecs=100): # 10x per second
 """
 in main thread: periodically check thread completions queue;
 do implied GUI actions on queue in this main GUI thread;
 one consumer (GUI), multiple producers (load,del,send);
 a simple list may suffice: list.append/pop are atomic;
 one action at a time here: a loop may block GUI temporarily;
 """
 try:
 (callback, args) = threadQueue.get(block=False)
 except Queue.Empty:
 pass
 else:
 callback(*args)
 widget.after(delayMsecs, lambda: threadChecker(widget))
def threaded(action, args, context, onExit, onFail, onProgress):
 """
 in a new thread: run action, manage thread queue puts;
 calls added to queue here are dispatched in main thread;
 run action with args now, later run on* calls with context;
 allows action to be ignorant of use as a thread here;
 passing callbacks into thread directly may update GUI in
 thread - passed func in shared memory but called in thread;
 progress callback just adds callback to queue with passed args;
 don't update counters here: not finished till taken off queue
 """
 try:
 if not onProgress: # wait for action in this thread
 action(*args) # assume raises exception if fails
 else:
 progress = (lambda *any: threadQueue.put((onProgress, any+context)))
 action(progress=progress, *args)
 except:
 threadQueue.put((onFail, (sys.exc_info(),)+context))
 else:
 threadQueue.put((onExit, context))
def startThread(action, args, context, onExit, onFail, onProgress=None):
 thread.start_new_thread(
 threaded, (action, args, context, onExit, onFail, onProgress)) 
class ThreadCounter:
 """
 a thread-safe counter or flag
 """
 def __init__(self):
 self.count = 0
 self.mutex = thread.allocate_lock() # or use Threading.semaphore
 def incr(self):
 self.mutex.acquire()
 self.count += 1
 self.mutex.release()
 def decr(self):
 self.mutex.acquire()
 self.count -= 1
 self.mutex.release()
 def __len__(self): return self.count # True/False if used as a flag
if __name__ == '__main__': # self-test code when run
 import time, ScrolledText
 
 def threadaction(id, reps, progress): # what the thread does
 for i in range(reps):
 time.sleep(1)
 if progress: progress(i) # progress callback: queued
 if id % 2 == 1: raise Exception # odd numbered: fail
 def mainaction(i): # code that spawns thread
 myname = 'thread-%s' % i
 startThread(threadaction, (i, 3),
 (myname,), threadexit, threadfail, threadprogress)
 # thread callbacks: dispatched off queue in main thread
 def threadexit(myname):
 root.insert('end', '%s\texit\n' % myname)
 root.see('end')
 def threadfail(exc_info, myname):
 root.insert('end', '%s\tfail\t%s\n' % (myname, exc_info[0]))
 root.see('end')
 def threadprogress(count, myname):
 root.insert('end', '%s\tprog\t%s\n' % (myname, count))
 root.see('end')
 root.update() # works here: run in main thread
 # make enclosing GUI 
 # spawn batch of worker threads on each mouse click: may overlap
 root = ScrolledText.ScrolledText()
 root.pack()
 threadChecker(root) # start thread loop in main thread
 root.bind('<Button-1>', lambda event: map(mainaction, range(6)))
 root.mainloop() # popup window, enter tk event loop



AltStyle によって変換されたページ (->オリジナル) /