• Home
  • Django
  • WxPython and threading by using event

WxPython and threading by using event

linkaiyi
Follow

Long running tasks with theading and event

Zeilennummern ein/ausschalten

   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.

Zeilennummern ein/ausschalten

   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]

Zeilennummern ein/ausschalten

   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()
Object has 0 attachments

Was this article helpful?

This article is viewed 38 times!

0 Comments

Leave a Comment

Support