python gui中线程的调用

2022-05-13 10:36:38 浏览数 (1)

""" ################################################################################# System-wide thread interface utilities for GUIs.

Implements a single thread callback queue and checker timer loop shared by all the windows in a program; worker threads queue their exit and progress actions to be run in the main thread; this doesn't block the GUI - it just spawns operations and manages and dispatches exits and progress; worker threads can overlap freely with the main thread, and with other workers.

Using a queue of callback functions and arguments 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 different implied exit actions.

Because GUI API is not completely thread-safe, instead of calling GUI update callbacks directly after thread main action, place them on a shared queue, to be run from a timer loop in the main thread, not a child thread; this also makes GUI update points less random and unpredictable; requires threads to be split into main action, exit actions, and progress action.

Assumes threaded action raises an exception on failure, and has a 'progress' callback argument if it supports progress updates; also assumes callbacks are either short-lived or update as they run, and that queue will contain callback functions (or other callables) for use in a GUI app - requires a widget in order to schedule and catch 'after' event loop callbacks; to use this model in non-GUI contexts, could use simple thread timer instead. ################################################################################# """

run even if no threads # in standard lib now

try: # raise ImportError to import _thread as thread # run with GUI blocking except ImportError: # if threads not available import _dummy_thread as thread # same interface, no threads

shared cross-process queue

named in shared global scope, lives in shared object memory

import queue, sys threadQueue = queue.Queue(maxsize=0) # infinite size

#################################################################################

IN MAIN THREAD - periodically check thread completions queue; run implied GUI

actions on queue in this main GUI thread; one consumer (GUI), and multiple

producers (load, del, send); a simple list may suffice too: list.append and

pop atomic?; 4E: runs at most N actions per timer event: looping through all

queued callbacks on each timer event may block GUI indefinitely, but running

only one can take a long time or consume CPU for timer events (e.g., progress);

assumes callback is either short-lived or updates display as it runs: after a

callback run, the code here reschedules and returns to event loop and updates;

because this perpetual loop runs in main thread, does not stop program exit;

#################################################################################

def threadChecker(widget, delayMsecs=100, perEvent=1): # 10x/sec, 1/timer for i in range(perEvent): # pass to set speed try: (callback, args) = threadQueue.get(block=False) # run <= N callbacks except queue.Empty: break # anything ready? else: callback(*args) # run callback here

代码语言:javascript复制
widget.after(delayMsecs,                                 # reset timer event
    lambda: threadChecker(widget, delayMsecs, perEvent)) # back to event loop

#################################################################################

IN A NEW THREAD - run action, manage thread queue puts for exits and progress;

run action with args now, later run on* calls with context; calls added to

queue here are dispatched in main thread only, to avoid parallel GUI updates;

allows action to be fully ignorant of use in a thread here; avoids running

callbacks in thread directly: may update GUI in thread, since passed func in

shared memory called in thread; progress callback just adds callback to queue

with passed args; don't update in-progress counters here: not finished till

exit actions taken off queue and dispatched in main thread by threadChecker;

#################################################################################

def threaded(action, args, context, onExit, onFail, onProgress): try: if not onProgress: # wait for action in this thread action(args) # assume raises exception if fails else: def progress(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))

#################################################################################

a thread-safe counter or flag: useful to avoid operation overlap if threads

update other shared state beyond that managed by the thread callback queue

#################################################################################

class ThreadCounter: def init(self): self.count = 0 self.mutex = thread.allocate_lock() # or use Threading.semaphore def incr(self): self.mutex.acquire() # or with self.mutex: 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

#################################################################################

self-test code: split thread action into main, exits, progress

#################################################################################

if name == 'main': # self-test code when run import time # or PP4E.Gui.Tour.scrolledtext from tkinter.scrolledtext import ScrolledText

代码语言:javascript复制
def onEvent(i):                             # code that spawns thread
    myname = 'thread-%s' % i
    startThread(
        action     = threadaction,
        args       = (i, 3),
        context    = (myname,),
        onExit     = threadexit,
        onFail     = threadfail,
        onProgress = threadprogress)

# thread's main action
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

# thread exit/progress callbacks: dispatched off queue in main thread
def threadexit(myname):
    text.insert('end', '%stexitn' % myname)
    text.see('end')

def threadfail(exc_info, myname):
    text.insert('end', '%stfailt%sn' % (myname, exc_info[0]))
    text.see('end')

def threadprogress(count, myname):
    text.insert('end', '%stprogt%sn' % (myname, count))
    text.see('end')
    text.update()   # works here: run in main thread

# make enclosing GUI and start timer loop in main thread
# spawn batch of worker threads on each mouse click: may overlap

text = ScrolledText()
text.pack()
threadChecker(text)                 # start thread loop in main thread
text.bind('<Button-1>',             # 3.x need list for map, range ok
          lambda event: list(map(onEvent, range(6))) )
text.mainloop()                     # pop-up window, enter tk event loop

tests thread callback queue, but uses class bound methods for action and callbacks

import time from tkinter.scrolledtext import ScrolledText

class MyGUI: def init(self, reps=3): self.reps = reps # uses default Tk root self.text = ScrolledText() # save widget as state self.text.pack() threadChecker(self.text) # start thread check loop self.text.bind('<Button-1>', # 3.x need list for map, range ok lambda event: list(map(self.onEvent, range(6))))

代码语言:javascript复制
def onEvent(self, i):  # code that spawns thread
    myname = 'thread-%s' % i
    startThread(
        action=self.threadaction,
        args=(i,),
        context=(myname,),
        onExit=self.threadexit,
        onFail=self.threadfail,
        onProgress=self.threadprogress)

# thread's main action
def threadaction(self, id, progress):  # what the thread does
    for i in range(self.reps):  # access to object state here
        time.sleep(1)
        if progress: progress(i)  # progress callback: queued
    if id % 2 == 1: raise Exception  # odd numbered: fail

# thread callbacks: dispatched off queue in main thread
def threadexit(self, myname):
    self.text.insert('end', '%stexitn' % myname)
    self.text.see('end')

def threadfail(self, exc_info, myname):  # have access to self state
    self.text.insert('end', '%stfailt%sn' % (myname, exc_info[0]))
    self.text.see('end')

def threadprogress(self, count, myname):
    self.text.insert('end', '%stprogt%sn' % (myname, count))
    self.text.see('end')
    self.text.update()  # works here: run in main thread

if name == 'main': MyGUI().text.mainloop()

gui

0 人点赞