• LongRunningTasks


    Introduction

    The following was posted to the comp.lang.python newsgroup on 24-May-2001. Since it explains very well the options for keeping the GUI responsive when the application has to do long running tasks I decided to copy it here verbatim. Many thanks to David Bolen for taking the time and effort to compose and post this message.

    --Robin

     

    The Question

    Daniel Frame writes:

    • Basically, I've constructed a GUI with wxPython which runs a very long function when a 'START' button is pressed. Of course, the GUI is 'locked up' until this function completes itself. I was wanting to add a 'STOP' button to my application which would halt this other function if the user got tired of waiting for it to complete. Is it possible to accomplish this without resorting to using threads? If so, How can I have my running function check occasionally to see if the stop button was pressed? I've heard of wxYield, but I can't seem to implement it properly.

     

    David's Response

    There's really three possibilities I would think of - threading, wxYield, or chunking up your processing in a wxEVT_IDLE handler. I've given a small sample of each below.

    I personally like threads for this sort of thing (I think they leave the UI the most responsive), but there's no hard and fast rule. So whichever you're more comfortable with.

    Threading really isn't that hard - you can just start up a thread to do your processing, and have it send an event to your main GUI thread when it has completed. While it is working, it can check an event object or some other flag variable to indicate that it should give up and stop.

    For a simple sort of example, here's a small bit of code that presents a simple (really dumb visually) frame, and uses a worker thread to simulate some processing (that takes 10s, resulting in a value of 10), while permitting it to be aborted. This could be extrapolated to doing any sort of lengthy processing and returning any sort of result. Intermediate events could be generated during the processing to give some indication to the main GUI thread of how things were proceeding (perhaps updating something like a gauge or some other visual indicator).

     

    切换行号显示
       1 import time
       2 from threading import *
       3 import wx
       4 
       5 # Button definitions
       6 ID_START = wx.NewId()
       7 ID_STOP = wx.NewId()
       8 
       9 # Define notification event for thread completion
      10 EVT_RESULT_ID = wx.NewId()
      11 
      12 def EVT_RESULT(win, func):
      13     """Define Result Event."""
      14     win.Connect(-1, -1, EVT_RESULT_ID, func)
      15 
      16 class ResultEvent(wx.PyEvent):
      17     """Simple event to carry arbitrary result data."""
      18     def __init__(self, data):
      19         """Init Result Event."""
      20         wx.PyEvent.__init__(self)
      21         self.SetEventType(EVT_RESULT_ID)
      22         self.data = data
      23 
      24 # Thread class that executes processing
      25 class WorkerThread(Thread):
      26     """Worker Thread Class."""
      27     def __init__(self, notify_window):
      28         """Init Worker Thread Class."""
      29         Thread.__init__(self)
      30         self._notify_window = notify_window
      31         self._want_abort = 0
      32         # This starts the thread running on creation, but you could
      33         # also make the GUI thread responsible for calling this
      34         self.start()
      35 
      36     def run(self):
      37         """Run Worker Thread."""
      38         # This is the code executing in the new thread. Simulation of
      39         # a long process (well, 10s here) as a simple loop - you will
      40         # need to structure your processing so that you periodically
      41         # peek at the abort variable
      42         for i in range(10):
      43             time.sleep(1)
      44             if self._want_abort:
      45                 # Use a result of None to acknowledge the abort (of
      46                 # course you can use whatever you'd like or even
      47                 # a separate event type)
      48                 wx.PostEvent(self._notify_window, ResultEvent(None))
      49                 return
      50         # Here's where the result would be returned (this is an
      51         # example fixed result of the number 10, but it could be
      52         # any Python object)
      53         wx.PostEvent(self._notify_window, ResultEvent(10))
      54 
      55     def abort(self):
      56         """abort worker thread."""
      57         # Method for use by main thread to signal an abort
      58         self._want_abort = 1
      59 
      60 # GUI Frame class that spins off the worker thread
      61 class MainFrame(wx.Frame):
      62     """Class MainFrame."""
      63     def __init__(self, parent, id):
      64         """Create the MainFrame."""
      65         wx.Frame.__init__(self, parent, id, 'Thread Test')
      66 
      67         # Dumb sample frame with two buttons
      68         wx.Button(self, ID_START, 'Start', pos=(0,0))
      69         wx.Button(self, ID_STOP, 'Stop', pos=(0,50))
      70         self.status = wx.StaticText(self, -1, '', pos=(0,100))
      71 
      72         self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
      73         self.Bind(wx.EVT_BUTTON, self.OnStop, id=ID_STOP)
      74 
      75         # Set up event handler for any worker thread results
      76         EVT_RESULT(self,self.OnResult)
      77 
      78         # And indicate we don't have a worker thread yet
      79         self.worker = None
      80 
      81     def OnStart(self, event):
      82         """Start Computation."""
      83         # Trigger the worker thread unless it's already busy
      84         if not self.worker:
      85             self.status.SetLabel('Starting computation')
      86             self.worker = WorkerThread(self)
      87 
      88     def OnStop(self, event):
      89         """Stop Computation."""
      90         # Flag the worker thread to stop if running
      91         if self.worker:
      92             self.status.SetLabel('Trying to abort computation')
      93             self.worker.abort()
      94 
      95     def OnResult(self, event):
      96         """Show Result status."""
      97         if event.data is None:
      98             # Thread aborted (using our convention of None return)
      99             self.status.SetLabel('Computation aborted')
     100         else:
     101             # Process results here
     102             self.status.SetLabel('Computation Result: %s' % event.data)
     103         # In either event, the worker is done
     104         self.worker = None
     105 
     106 class MainApp(wx.App):
     107     """Class Main App."""
     108     def OnInit(self):
     109         """Init Main App."""
     110         self.frame = MainFrame(None, -1)
     111         self.frame.Show(True)
     112         self.SetTopWindow(self.frame)
     113         return True
     114 
     115 if __name__ == '__main__':
     116     app = MainApp(0)
     117     app.MainLoop()
    

    Oh, and if you're concerned with hanging on an exit if your thread doesn't terminate for some reason, just add a "self.setDaemon(1)" to the init and Python won't wait for it to terminate.

    The second approach, using wxYield, should be fine too - just add a call to wxYield() somewhere within the computation code such that it executes periodically. At that point, any pending window events will be dispatched (permitting the window to refresh, process button presses, etc...). Then, it's similar to the above in that you set a flag so that when the original code gets control after the wxYield() returns it knows to stop processing.

    As with the threading case, since all events go through during the wxYield() you need to protect against trying to run the same operation twice.

    Here's the equivalent of the above but placing the computation right inside the main window class. Note that one difference is that unlike with threading, the responsiveness of your GUI is now directly related to how frequently you call wxYield, so you may have delays refreshing your window dependent on that frequency. You should notice that this is a bit more sluggish with its frequency of a wxYield() each second.

     

    切换行号显示
       1 import time
       2 import wx
       3 
       4 # Button definitions
       5 ID_START = wx.NewId()
       6 ID_STOP = wx.NewId()
       7 
       8 # GUI Frame class that spins off the worker thread
       9 class MainFrame(wx.Frame):
      10     """Class MainFrame."""
      11     def __init__(self, parent, id):
      12         """Create the MainFrame."""
      13         wx.Frame.__init__(self, parent, id, 'wxYield Test')
      14 
      15         # Dumb sample frame with two buttons
      16         wx.Button(self, ID_START, 'Start', pos=(0,0))
      17         wx.Button(self, ID_STOP, 'Stop', pos=(0,50))
      18         self.status = wx.StaticText(self, -1, '', pos=(0,100))
      19 
      20         self.Bind (wx.EVT_BUTTON, self.OnStart, id=ID_START)
      21         self.Bind (wx.EVT_BUTTON, self.OnStop, id=ID_STOP)
      22 
      23         # Indicate we aren't working on it yet
      24         self.working = 0
      25 
      26     def OnStart(self, event):
      27         """Start Computation."""
      28         # Start the processing - this simulates a loop - you need to call
      29         # wx.Yield at some periodic interval.
      30         if not self.working:
      31             self.status.SetLabel('Starting Computation')
      32             self.working = 1
      33             self.need_abort = 0
      34 
      35             for i in range(10):
      36                 time.sleep(1)
      37                 wx.Yield()
      38                 if self.need_abort:
      39                     self.status.SetLabel('Computation aborted')
      40                     break
      41             else:
      42                 # Here's where you would process the result
      43                 # Note you should only do this if not aborted.
      44                 self.status.SetLabel('Computation Completed')
      45 
      46             # In either event, we aren't running any more
      47             self.working = 0
      48 
      49     def OnStop(self, event):
      50         """Stop Computation."""
      51         if self.working:
      52             self.status.SetLabel('Trying to abort computation')
      53             self.need_abort = 1
      54 
      55 class MainApp(wx.App):
      56     """Class Main App."""
      57     def OnInit(self):
      58         """Init Main App."""
      59         self.frame = MainFrame(None,-1)
      60         self.frame.Show(True)
      61         self.SetTopWindow(self.frame)
      62         return True
      63 
      64 if __name__ == '__main__':
      65     app = MainApp(0)
      66     app.MainLoop()
    

    And finally, you can do your work within an idle handler. In this case, you let wxPython generate an IDLE event whenever it has completed processing normal user events, and then you perform a "chunk" of your processing in each such case. This can be a little tricker depending on your algorithm since you have to be able to perform the work in discrete pieces. Inside your IDLE handler, you request that it be called again if you aren't done, but you want to make sure that each pass through the handler doesn't take too long.Effectively, each event is similar to the gap between wxYield() calls in the previous example, and your GUI responsiveness will be subject to that latency just as with the wxYield() case.

    I'm also not sure you can remove an idle handler once established (or at least I think I had problems with that in the past), so the code below just establishes it once and the handler only does work if it's in the midst of a computation. [Actually, you can use the Disconnect method to remove an event handler binding, although there is no real need to do so as there is very little overhead if you use a guard condition as in the code below. --Robin]

     

    切换行号显示
       1 import time
       2 import wx
       3 
       4 # Button definitions
       5 ID_START = wx.NewId()
       6 ID_STOP = wx.NewId()
       7 
       8 # GUI Frame class that spins off the worker thread
       9 class MainFrame(wx.Frame):
      10     """Class MainFrame."""
      11     def __init__(self, parent, id):
      12         """Create the MainFrame."""
      13         wx.Frame.__init__(self, parent, id, 'Idle Test')
      14 
      15         # Dumb sample frame with two buttons
      16         wx.Button(self, ID_START, 'Start',p os=(0,0))
      17         wx.Button(self, ID_STOP, 'Stop', pos=(0,50))
      18         self.status = wx.StaticText(self, -1, '', pos=(0,100))
      19 
      20         self.Bind (wx.EVT_BUTTON, self.OnStart, id=ID_START)
      21         self.Bind (wx.EVT_BUTTON, self.OnStop, id=ID_STOP)
      22         self.Bind (wx.EVT_IDLE, self.OnIdle)
      23 
      24         # Indicate we aren't working on it yet
      25         self.working = 0
      26 
      27     def OnStart(self, event):
      28         """Start Computation."""
      29         # Set up for processing and trigger idle event
      30         if not self.working:
      31             self.status.SetLabel('Starting Computation')
      32             self.count = 0
      33             self.working = 1
      34             self.need_abort = 0
      35 
      36     def OnIdle(self, event):
      37         """Idle Handler."""
      38         if self.working:
      39             # This is where the processing takes place, one bit at a time
      40             if self.need_abort:
      41                 self.status.SetLabel('Computation aborted')
      42             else:
      43                 self.count = self.count + 1
      44                 time.sleep(1)
      45                 if self.count < 10:
      46                     # Still more work to do so request another event
      47                     event.RequestMore()
      48                     return
      49                 else:
      50                     self.status.SetLabel('Computation completed')
      51 
      52             # Reaching here is an abort or completion - end in either case
      53             self.working = 0
      54 
      55     def OnStop(self, event):
      56         """Stop Computation."""
      57         if self.working:
      58             self.status.SetLabel('Trying to abort computation')
      59             self.need_abort = 1
      60 
      61 class MainApp(wx.App):
      62     """Class Main App."""
      63     def OnInit(self):
      64         """Init Main App."""
      65         self.frame = MainFrame(None, -1)
      66         self.frame.Show(True)
      67         self.SetTopWindow(self.frame)
      68         return True
      69 
      70 if __name__ == '__main__':
      71     app = MainApp(0)
      72     app.MainLoop()
    

     


     

     

    Slight modification for recursive tasks

    Jeff Grimmett adds:

    The above three examples got me going in the right direction, but the actual worker task used is a little on the simple side for what I needed to do, which was to recurse through directories and use the same class for each recursion.

    Do I need to tell you that it's a bad idea to recurse 100 threads at once? :-) Highly entertaining to watch but hardly useful of the end goal is to collect data serially!

    The trick I settled on was to seperate the thread from the task. The means of communicating the shutdown command was the first thing I tackled. I settled on the python threading module's Event class. This class is basically a flag, which i suppose would also work quite well as long as the scope is correct.

     

    切换行号显示
       1 import  threading
       2 
       3 # This is a global flag that we will manipulate as needed
       4 # to allow graceful exit from the recursive search.
       5 KeepRunning     =       threading.Event()
    

    The next thing I needed to do was define the scope of the actual worker thread.

     

    切换行号显示
       1 class   Worker(threading.Thread):
       2   def __init__ (self, op, dir1, dir2):
       3   threading.Thread.__init__(self)
       4   self.op   = op
       5   self.Dir1 = dir1
       6   self.Dir2 = dir2
       7   KeepRunning.set()
       8 
       9   self.start()
      10 
      11   def run(self):
      12     self.wb = WorkerBee(self.op, self.Dir1, self.Dir2)
      13     self.op.AppendText('Done!\n')
      14     wxBell()
      15 
      16     # Assuming you are following the above example somewhat, assume that this is a
      17     # similar 'reporting' event class that we are calling. It carries a cargo which
      18     # is in fact the 'head' WorkerBee.
      19     wxPostEvent(self.op.GetParent(), NewReport(self.wb))
      20 
      21   def abort(self):
      22     KeepRunning.clear()
      23 
      24   # print to the output window.
      25   def Update(self, txt):
      26     self.op.AppendText(txt)
      27     self.op.ShowPosition(self.op.GetLastPosition()) # keeps the last line visible
    

    The thread initializes with a pointer to the output window - which in this case is a wxTextCtrl() object - and the starting directory. Like the above example, this thread is self-starting, but need not be.

    The first big difference is in the run() method. In the above example all the work is actually done BY the run() method. In this case, we create a WorkerBee object and retain a handle on it. Exactly why will become clear in a moment.

    The second difference is that instead of keeping our 'keep running' variable local, we now have it global in the form of the KeepGoing Event object. Again, the reason why becomes clear in the next part.

    While not directly on topic, the Update() method is simply a convenience to deal with the output display. If you were going to stdout this would be entirely uncessesary.

    ====

    The WorkerBee class is the main point of difference between this example and the previous. Instead of the thread object doing all the work, it starts up the WorkerBee object.

    The WorkerBee class recursively scans two directories and compares the results using the filecmp Python module. The exact task is not important, but I had to use something :-)

     

    切换行号显示
       1 class WorkerBee:
       2   def __init__ (self, op, dir1, dir2):
       3     self.DiffList = []  # We will retain a list of changed files
       4     self.DirList  = []  # We will retain a list of directories.
       5     self.A        = dir1
       6     self.B        = dir2
       7     self.op       = op
       8 
       9     self.Update('scanning %s\n' % self.A)
      10 
      11     self.cmp = filecmp.dircmp(self.A, self.B)
      12 
      13     self.Deleted = self.cmp.left_only
      14     self.New     = self.cmp.right_only
      15     self.Files   = self.cmp.common_files
      16     self.Dirs    = self.cmp.common_dirs
      17 
      18     for i in self.Files :
      19      if not KeepRunning.isSet(): break
      20 
      21        self.Update('\t%s\\%s' %(self.A,i))
      22 
      23        if filecmp.cmp('%s\\%s' % (self.A, i), '%s\\%s' % (self.B, i), shallow=0) == 0 :
      24          self.Update('\t<---- DIFF ***\n')      # A diff!
      25          self.DiffList.append(i)
      26        else:
      27          self.Update('\n')
      28 
      29     for i in self.Dirs  :
      30       if not KeepRunning.isSet(): break
      31 
      32       self.DirList.append ( WorkerBee ( op,
      33                                         '%s\\%s' % (self.A, i),
      34                                         '%s\\%s' % (self.B, i)
      35                                       )
      36                           )
      37 
      38   def   Update(self, txt):
      39     self.op.AppendText(txt)
      40     self.op.ShowPosition(self.op.GetLastPosition())
    

    THIS is why we need to keep the thread object and the workerbee seperate. If the workerbee was built around the Thread class, we would have a SWARM of workerbees! However, in this case what we end up with is a controlled recursion into a directory tree, with frequent checks on the KeepRunning Event flag. The whole thing shuts down quite nicely and leaves the GUI VERY responsive.

     

    More Tips

    ChuckEsterbrook asked:

    The LongRunningTasks wiki page doesn't mention using Queues. When people use this technique what is the set up? For example, is your "worker thread" pushing data into the queue and then a wx.Timer is polling it out with a non-blocking q.get_nowait()?

    RobinDunn replied:

    That's one way. Another is to call wxWakeUpIdle when the item is put in the Queue, and then have a EVT_IDLE handler that fetches items from the queue. That way the item is usually processed very soon after it is put there without having to have a higher frequency timer.

    Another option is to not use the queue or wxPostEvent and just use wxCallAfter passing to it the callable and parameters to be called in the GUI thread. (It uses wxPostEvent internally.) The implementation and docs of wxCallAfter is in core.py.

    MarienZwart would like to add:

    This is actually (sort of) documented in the wxwidgets docs:

    • For communication between secondary threads and the main thread, you may use wxEvtHandler::AddPendingEvent or its short version wxPostEvent. These functions have a thread-safe implementation so that they can be used as they are for sending events from one thread to another. However there is no built in method to send messages to the worker threads and you will need to use the available synchronization classes to implement the solution which suits your needs yourself.

    So you do not need a Queue to send messages from the worker thread to the main/gui thread: just use AddPendingEvent in the worker and a normal event handler in the gui. However if you need to send messages from the main/gui thread to the worker thread the right way to do it is probably a Queue, with the gui thread calling put() and the worker regularly checking for messages with get_nowait(). You can *probably* get away with using a simple boolean flag like some examples on this page use without any extra locking thanks to the GIL (Global Interpreter Lock), but for anything more complicated than that you need locking (like the Event used in the previous example), and for more than one kind of "message" a queue is usually the simplest way to go. Even something as simple as a worker thread that can be paused, resumed and cancelled is IMHO simpler using a queue than using a bunch of Event and/or Condition objects (the worker can just .get() on the queue to pause).

     


     

    See also Brian Kelley's demo (Process.py) which shows how to do a long task in a separate process (using wxProcess and wxExecute) rather than a thread. A separate process is like running a separate application - it can run full speed, whereas a thread might be slowed about 30% by Python's global interpreter lock (I don't know which version you were using, but at least for the first few threads, Python does pretty well, resulting in fairly minimal overhead - Josiah).

    MVoncken suggests: I prefer using a generator,(variant on wxYield,Example2):

    切换行号显示
       1         def DoSomeLongTask(self):
       2             """
       3             usualy defined somewhere else
       4             """
       5             for i in range(10):
       6                  time.sleep(1)
       7                  yield i
       8 
       9         def OnStart(self, event):
      10             # Over-Simplified version.
      11             # iterate over DoSomeLongTask,call wxYield and display status
      12             for status in DoSomeLongTask():
      13                 #display status here.. (wx.ProgressBar?)  
      14                 wxYield()
      15                 if self.need_abort:
      16                         break
    

     


     

    While using a generator and wx.Yield() works, you are limited to having a single long-running-task at any time. This may be a serious limitation to your application. Instead, you can use the built-in wx.FutureCall() function to schedule as many calls as you want/need. I use this in to handle 'replace all' in one of my applications.

    Josiah suggests:

     

    切换行号显示
       1     def DoSomeLongTask(self, state):
       2         #pull the state out
       3         x,y,z = state
       4         if not self.need_abort:
       5             time.sleep(1)
       6             x -= 1
       7             if x > 0 and not self.need_abort:
       8                 wx.FutureCall(1, self.DoSomeLongTask, (x,y,z))
       9 
      10     def OnStart(self, event):
      11         # Over-Simplified version.
      12         self._DoSomeLongTask((10, 1, 2))
    

    Alternatively, since wx.FutureCall creates and manipulates wx.Timer instances, we could just create a registry of stuff to call when a timer expires, and allow insertion of items to call into this registry. Why do we use a Timer/FutureCall instead of wx.CallAfter? It's mostly a matter of taste, but we use self.IsRunning() rather than keeping explicit state as a convenience.

    Josiah also suggests:

     

    切换行号显示
       1 import traceback
       2 
       3 class CallRegistry(wx.Timer):
       4     def __init__(self, delay=1):
       5         wx.Timer.__init__(self)
       6         self.tasks = []
       7         if delay < 1:
       8             delay = 1
       9         self.delay = delay
      10 
      11     def Notify(self):
      12         tl = []
      13         otl = self.tasks
      14         self.tasks = []
      15         for x in otl:
      16             try:
      17                 i,j = x
      18                 x = i(j)
      19                 if x:
      20                     try:
      21                         i,j = x
      22                     except:
      23                         if callable(x):
      24                             tl.append((x, None))
      25                     else:
      26                         tl.append(x)
      27             except (KeyboardInterrupt, SystemExit):
      28                 raise
      29             except:
      30                 traceback.print_exc()
      31         self.tasks.extend(tl)
      32         if not self.tasks:
      33             self.Stop()
      34 
      35     def CallLater(self, fcn, state=None):
      36         self.tasks.append((fcn, state))
      37         if not self.IsRunning():
      38             self.Start(self.delay, wx.TIMER_CONTINUOUS)
    

    Sample use of the above:

     

    切换行号显示
       1 class MyFrame(wx.Frame):
       2     def __init__(self, ...):
       3         ...
       4         self.cr = CallRegistry()
       5         self.cr.CallLater(self.Task1)
       6         self.cr.CallLater(self.Task2)
       7         ...
       8 
       9     def Task1(self, state):
      10         ...
      11         #automatically reschedules itself
      12         return self.Task1, newstate
      13 
      14     def Task2(self, state):
      15         ...
      16         if c1:
      17             #schedules some other task without arguments
      18             return self.Task3
      19         else:
      20             #reschedules itself again
      21             return self.Task2, newstate
    

     

    Redirecting text from stdout to a wx.TextCtrl

    I thought I might add this to the discussion. There are probably more ways than one to do this but... This is some sample code that I put together that updates a text control in wxpython from output being sent to stdout through backend "print" statments being executed in a separate thread.

    The idea here is to that you're creating a class with a "write" method to rebind to stdout (the real value of stdout is always kept in sys.stdout for rebinding). Everytime a message is spit out to stdout an event is thrown out to the text control for processing. The problem is that the text control doesn't update itself. Therefore we need to get the control to process its pending events. Therefore we need to set up a timer that gets the text control to attempt to process its pending events every so often.

    The threading code is pulled from Python in a Nutshell pretty much (which is what I'm using). It uses 2 Queues to process events. requestID, callable, args, kwds = self.requestQ.get() will suspend the thread untill the callable returns, and when it completes self.resultQ.put((requestID, callable(*args, **kwds))) will tell us that the process has finished its task. When this is done we submit an event to the GUI telling us that we are done.

    There's better ways to poll the resultQ but for my purposes here this works pretty nicely. Anyway hope this is useful to someone...

     

    切换行号显示
       1 #!/usr/bin/env python
       2 
       3 import wx, threading, Queue, sys, time
       4 from wx.lib.newevent import NewEvent
       5 
       6 ID_BEGIN=100
       7 wxStdOut, EVT_STDDOUT= NewEvent()
       8 wxWorkerDone, EVT_WORKER_DONE= NewEvent()
       9 
      10 def LongRunningProcess(lines_of_output):
      11     for x in range(lines_of_output):
      12         print "I am a line of output (hi!)...."
      13         time.sleep(1)
      14 
      15 class MainFrame(wx.Frame):
      16     def __init__(self, parent, id, title):
      17         wx.Frame.__init__(self, parent, id, title, size=(300, 300))
      18         self.requestQ = Queue.Queue() #create queues
      19         self.resultQ = Queue.Queue()
      20 
      21         #widgets
      22         p = wx.Panel(self)
      23         self.output_window = wx.TextCtrl(p, -1,
      24                              style=wx.TE_AUTO_SCROLL|wx.TE_MULTILINE|wx.TE_READONLY)
      25         self.go = wx.Button(p, ID_BEGIN, 'Begin')
      26         self.output_window_timer = wx.Timer(self.output_window, -1)
      27 
      28         #frame sizers
      29         sizer = wx.BoxSizer(wx.VERTICAL)
      30         sizer.Add(self.output_window, 10, wx.EXPAND)
      31         sizer.Add(self.go, 1, wx.EXPAND)
      32         p.SetSizer(sizer)
      33 
      34         #events
      35         wx.EVT_BUTTON(self, ID_BEGIN, self.OnBeginTest)
      36         self.output_window.Bind(EVT_STDDOUT, self.OnUpdateOutputWindow)
      37         self.output_window.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
      38         self.Bind(EVT_WORKER_DONE, self.OnWorkerDone)
      39 
      40         #thread
      41         self.worker = Worker(self, self.requestQ, self.resultQ)
      42 
      43     def OnUpdateOutputWindow(self, event):
      44         value = event.text
      45         self.output_window.AppendText(value)
      46 
      47     def OnBeginTest(self, event):
      48         lines_of_output=7
      49         self.go.Disable()
      50         self.worker.beginTest(LongRunningProcess, lines_of_output)
      51         self.output_window_timer.Start(50)
      52 
      53     def OnWorkerDone(self, event):
      54         self.output_window_timer.Stop()
      55         self.go.Enable()
      56 
      57     def OnProcessPendingOutputWindowEvents(self, event):
      58         self.output_window.ProcessPendingEvents()
      59 
      60 class Worker(threading.Thread):
      61     requestID = 0
      62     def __init__(self, parent, requestQ, resultQ, **kwds):
      63         threading.Thread.__init__(self, **kwds)
      64         self.setDaemon(True)
      65         self.requestQ = requestQ
      66         self.resultQ = resultQ
      67         self.start()
      68 
      69     def beginTest(self, callable, *args, **kwds):
      70         Worker.requestID +=1
      71         self.requestQ.put((Worker.requestID, callable, args, kwds))
      72         return Worker.requestID
      73 
      74     def run(self):
      75         while True:
      76             requestID, callable, args, kwds = self.requestQ.get()
      77             self.resultQ.put((requestID, callable(*args, **kwds)))
      78             evt = wxWorkerDone()
      79             wx.PostEvent(wx.GetApp().frame, evt)
      80 
      81 class SysOutListener:
      82     def write(self, string):
      83         sys.__stdout__.write(string)
      84         evt = wxStdOut(text=string)
      85         wx.PostEvent(wx.GetApp().frame.output_window, evt)
      86 
      87 class MyApp(wx.App):
      88     def OnInit(self):
      89         self.frame = MainFrame(None, -1, 'rebinding stdout')
      90         self.frame.Show(True)
      91         self.frame.Center()
      92         return True
      93 
      94 #entry point
      95 if __name__ == '__main__':
      96     app = MyApp(0)
      97     sys.stdout = SysOutListener()
      98     app.MainLoop()
    

     

    Easiest Implementation *Ever* :)

    So you say you want to perform a long running task from a wxPython GUI? Well, it don't get no simpler than this! Use threads but don't mess with a separate thread class. Make sure if you're running in the other thread that you interact with the GUI via wx.CallAfter.

     

    切换行号显示
       1 import wx
       2 import thread
       3 from time import sleep
       4 
       5 class MainFrame(wx.Frame):
       6 
       7     def __init__(self, parent):
       8         wx.Frame.__init__(self, parent)
       9 
      10         self.label = wx.StaticText(self, label="Ready")
      11         self.btn = wx.Button(self, label="Start")
      12         self.gauge = wx.Gauge(self)
      13 
      14         sizer = wx.BoxSizer(wx.VERTICAL)
      15         sizer.Add(self.label, proportion=1, flag=wx.EXPAND)
      16         sizer.Add(self.btn, proportion=0, flag=wx.EXPAND)
      17         sizer.Add(self.gauge, proportion=0, flag=wx.EXPAND)
      18 
      19         self.SetSizerAndFit(sizer)
      20 
      21         self.Bind(wx.EVT_BUTTON, self.onButton)
      22 
      23     def onButton(self, evt):
      24         self.btn.Enable(False)
      25         self.gauge.SetValue(0)
      26         self.label.SetLabel("Running")
      27         thread.start_new_thread(self.longRunning, ())
      28 
      29     def onLongRunDone(self):
      30         self.gauge.SetValue(100)
      31         self.label.SetLabel("Done")
      32         self.btn.Enable(True)
      33 
      34     def longRunning(self):
      35         """This runs in a different thread.  Sleep is used to simulate a long running task."""
      36         sleep(3)
      37         wx.CallAfter(self.gauge.SetValue, 20)
      38         sleep(5)
      39         wx.CallAfter(self.gauge.SetValue, 50)
      40         sleep(1)
      41         wx.CallAfter(self.gauge.SetValue, 70)
      42         sleep(10)
      43         wx.CallAfter(self.onLongRunDone)
      44 
      45 if __name__ == "__main__":
      46     app = wx.PySimpleApp()
      47     app.TopWindow = MainFrame(None)
      48     app.TopWindow.Show()
      49     app.MainLoop()
    

     

    THE Easiest Implementation *Ever* :-)))

    ...no, really. All (most) of the above cases are one-way communication examples. Sometimes, you just want the main thread to be non-gui and communicate with the gui in a blocking manner. See http://radekpodgorny.blogspot.cz/2012/12/working-with-wxpython-in-separate-thread.html on how to achieve that.

    /!\ update: robin was so kind to comment on my solution. i'll investigate all his ideas and probably refactor/improve the code. stay tuned and THANK YOU, robin! ;-)

  • 相关阅读:
    Java中断机制
    RPC原理
    synchronized和ReentrantLock的区别
    dubbo入门
    Zookeeper入门
    分布式事务
    Mysql索引会失效的几种情况
    java代码执行过慢的问题定位
    持续集成
    Mycat 数据库分库分表中间件
  • 原文地址:https://www.cnblogs.com/dengyigod/p/3094009.html
Copyright © 2020-2023  润新知