By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
443,907 Members | 1,832 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 443,907 IT Pros & Developers. It's quick & easy.

wxPython - problem w/ timers

P: 7
I get the following error in my app:
Expand|Select|Wrap|Line Numbers
  1. wx._core.PyAssertionError: C++ assertion "wxThread::IsMain()" failed at ..\..\sr
  2. c\common\timercmn.cpp(66) in wxTimerBase::Start(): timer can only be started fro
  3. m the main thread
The origin of the error is obvious enough but I can't think of any reason I'm not allowed to start timers from threads other than main if I so wish. Is there any going around this limitation?

TIA,
Greg
Nov 16 '07 #1
Share this Question
Share on Google+
13 Replies


bartonc
Expert 5K+
P: 6,596
The only reason that I can come up with is that the docs say:
Note: A timer can only be used from the main thread.
Nov 17 '07 #2

P: 7
The only reason that I can come up with is that the docs say:
Apparently there's no going around that, the timers just won't start. However, I thought about using standard Python threading timers, I wonder if they're safe to use with wxPython?
Nov 18 '07 #3

bartonc
Expert 5K+
P: 6,596
Apparently there's no going around that, the timers just won't start. However, I thought about using standard Python threading timers, I wonder if they're safe to use with wxPython?
Ya know; I've been working on a project that needed a timer in a module that lacked a wx event handler. threading.Timer is one option (definitely thread safe), but you'll need to be careful as to how you implement the recurrence and the ability to stop it. I also looked into sched.scheduler which has nicely documented error handling. I'll whip up a threaded test to see how that might work.
Nov 18 '07 #4

bartonc
Expert 5K+
P: 6,596
Ya know; I've been working on a project that needed a timer in a module that lacked a wx event handler. threading.Timer is one option (definitely thread safe), but you'll need to be careful as to how you implement the recurrence and the ability to stop it. I also looked into sched.scheduler which has nicely documented error handling. I'll whip up a threaded test to see how that might work.
It looks thread-safe to me (actually, forgive the slopy use threading in the BG_Timer class - I got in a rush there):
Expand|Select|Wrap|Line Numbers
  1. #-----------------------------------------------------------------------------
  2. # Name:        sched_thread_test.py
  3. # Purpose:     Test sched.Scheduler in a thread
  4. #
  5. # Author:      Barton
  6. #
  7. # Created:     2007/11/18
  8. # RCS-ID:      $Id: sched_thread_test.py $
  9. # Copyright:   (c) 2007
  10. # Licence:     Free
  11. #-----------------------------------------------------------------------------
  12. import sched, time
  13. import threading
  14.  
  15. class BG_Timer(object):
  16.     def __init__(self, interval, actionFuct, *args):
  17.         self.interval = interval
  18.         self.actionFuct = actionFuct
  19.         self.timer = sched.scheduler(time.time, time.sleep)
  20.         self.timer.enter(interval, 1, self.SimEvent, args)
  21.         self.running = False
  22.         self.runTread = threading.Thread(target=self.Run)
  23.  
  24.     def SimEvent(self, *args):
  25.         """Reschedule this handler and call the action fuction"""
  26.         startTime = time.time()
  27.         self.actionFuct(args)
  28.         interval = self.interval - (time.time() - startTime)
  29.         if self.running:
  30.             self.timer.enter(interval, 1, self.SimEvent, args)
  31.  
  32.     def Run(self):
  33.         self.running = True
  34.         self.timer.run()
  35.  
  36.     def Start(self):
  37.         self.runTread.start()
  38.  
  39.     def Stop(self):
  40.         self.running = False
  41.  
  42. def BG_Task(thrdEvent):
  43.     def periodicTask(*args):
  44.         print time.time()
  45.  
  46.     t = BG_Timer(.25, periodicTask)
  47.     print 'starting bg'
  48.     t.Start()
  49.     print 'running bg'
  50.     thrdEvent.wait()
  51.     print 'stopping bg'
  52.     t.Stop()
  53.  
  54.  
  55. threadingEvent = threading.Event()
  56. thrd = threading.Thread(target=BG_Task, args=(threadingEvent,))
  57. print 'starting thread'
  58. thrd.start()
  59. time.sleep(2)
  60. print 'stopping thread'
  61. threadingEvent.set()
  62. thrd.join()
  63.  
Nov 19 '07 #5

bartonc
Expert 5K+
P: 6,596
It looks thread-safe to me (actually, forgive the slopy use threading in the BG_Timer class - I got in a rush there):
Here's the cleaned up version:
Expand|Select|Wrap|Line Numbers
  1. class BG_Timer(object):
  2.     def __init__(self, interval, actionFuct, *args):
  3.         self.interval = interval
  4.         self.actionFuct = actionFuct
  5.         self.timer = sched.scheduler(time.time, time.sleep)
  6.         self.timer.enter(interval, 1, self.SimEvent, args)
  7.         self.thrdEvent = threading.Event()
  8.         self.runTread = threading.Thread(target=self.Run, args=(self.thrdEvent, self.timer))
  9.  
  10.     def SimEvent(self, *args):
  11.         """Reschedule this handler and call the action fuction"""
  12.         startTime = time.time()
  13.         self.actionFuct(args)
  14.         interval = self.interval - (time.time() - startTime)
  15.         if self.thrdEvent.isSet():
  16.             self.timer.enter(interval, 1, self.SimEvent, args)
  17.  
  18.     def Run(self, thrdEvent, timer):
  19.         thrdEvent.set()
  20.         timer.run()
  21.  
  22.     def Start(self):
  23.         self.runTread.start()
  24.  
  25.     def Stop(self):
  26.         self.thrdEvent.clear()
  27.  
Nov 19 '07 #6

bartonc
Expert 5K+
P: 6,596
Here's the cleaned up version:
Expand|Select|Wrap|Line Numbers
  1. class BG_Timer(object):
  2.     def __init__(self, interval, actionFuct, *args):
  3.         self.interval = interval
  4.         self.actionFuct = actionFuct
  5.         self.timer = sched.scheduler(time.time, time.sleep)
  6.         self.timer.enter(interval, 1, self.SimEvent, args)
  7.         self.thrdEvent = threading.Event()
  8.         self.runTread = threading.Thread(target=self.Run, args=(self.thrdEvent, self.timer))
  9.  
  10.     def SimEvent(self, *args):
  11.         """Reschedule this handler and call the action fuction"""
  12.         startTime = time.time()
  13.         self.actionFuct(args)
  14.         interval = self.interval - (time.time() - startTime)
  15.         if self.thrdEvent.isSet():
  16.             self.timer.enter(interval, 1, self.SimEvent, args)
  17.  
  18.     def Run(self, thrdEvent, timer):
  19.         thrdEvent.set()
  20.         timer.run()
  21.  
  22.     def Start(self):
  23.         self.runTread.start()
  24.  
  25.     def Stop(self):
  26.         self.thrdEvent.clear()
  27.  
While I was out cleaning the barn, I realized that I hadn't addressed the core question of "safe with wx". So I put the normal if __name__ == "__main__": guard into the original module. And as expected, everything worked great:
Expand|Select|Wrap|Line Numbers
  1. #-----------------------------------------------------------------------------
  2. # Name:        SchedThreadTestFrame.py
  3. # Purpose:     Test sched.Scheduler in a thread in wx
  4. #
  5. # Author:      <your name>
  6. #
  7. # Created:     2007/11/18
  8. # RCS-ID:      $Id: SchedThreadTestFrame.py $
  9. # Copyright:   (c) 2007
  10. # Licence:     <your licence>
  11. #-----------------------------------------------------------------------------
  12. #Boa:Frame:Frame1
  13.  
  14. import wx
  15.  
  16. import threading
  17. import time
  18.  
  19. from sched_thread_test import BG_Timer
  20.  
  21. def create(parent):
  22.     return Frame1(parent)
  23.  
  24. [wxID_FRAME1, wxID_FRAME1BUTTON1, wxID_FRAME1PANEL1, 
  25. ] = [wx.NewId() for _init_ctrls in range(3)]
  26.  
  27. class Frame1(wx.Frame):
  28.     def _init_ctrls(self, prnt):
  29.         # generated method, don't edit
  30.         wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt, pos=wx.Point(132, 132),
  31.                 size=wx.Size(400, 250), style=wx.DEFAULT_FRAME_STYLE,
  32.                 title='Sched Thread Test Frame')
  33.         self.SetClientSize(wx.Size(392, 223))
  34.         self.Bind(wx.EVT_CLOSE, self.OnFrame1Close)
  35.  
  36.         self.panel1 = wx.Panel(id=wxID_FRAME1PANEL1, name='panel1', parent=self, pos=wx.Point(0, 0),
  37.                 size=wx.Size(392, 223), style=wx.TAB_TRAVERSAL)
  38.  
  39.         self.button1 = wx.Button(id=wxID_FRAME1BUTTON1, label='End Task', name='button1',
  40.                 parent=self.panel1, pos=wx.Point(104, 72), size=wx.Size(144, 24), style=0)
  41.         self.button1.Bind(wx.EVT_BUTTON, self.OnButton1, id=wxID_FRAME1BUTTON1)
  42.  
  43.     def __init__(self, parent):
  44.         self._init_ctrls(parent)
  45.  
  46.         self.threadingEvent = threading.Event()
  47.         self.thrd = threading.Thread(target=self.BG_Task, args=(self.threadingEvent,))
  48.         print 'starting thread'
  49.         self.thrd.start()
  50.  
  51.  
  52.     def BG_Task(self, thrdEvent):
  53.         def periodicTask(*args):
  54.             print time.time()
  55.  
  56.         t = BG_Timer(.25, periodicTask)
  57.         print 'starting bg'
  58.         t.Start()
  59.         print 'running bg'
  60.         thrdEvent.wait()
  61.         print 'stopping bg'
  62.         t.Stop()
  63.  
  64.     def OnButton1(self, event):
  65.         print 'stopping thread'
  66.         self.threadingEvent.set()
  67.         self.thrd.join()
  68.  
  69.     def OnFrame1Close(self, event):
  70.         self.OnButton1(None)
  71.         event.Skip()
  72.  
Nov 19 '07 #7

bartonc
Expert 5K+
P: 6,596
While I was out cleaning the barn, I realized that I hadn't addressed the core question of "safe with wx". So I put the normal if __name__ == "__main__": guard into the original module. And as expected, everything worked great:
And here's a neat trick that redirects stdout to a wxWindow. It comes in very handy for these types of text based tests:
Expand|Select|Wrap|Line Numbers
  1. #-----------------------------------------------------------------------------
  2. # Name:        wxSchedThreadTest.py
  3. # Purpose:     The wxApp
  4. #
  5. # Author:      <your name>
  6. #
  7. # Created:     2007/11/19
  8. # RCS-ID:      $Id: wxSchedThreadTest.py $
  9. # Copyright:   (c) 2007
  10. # Licence:     <your licence>
  11. #-----------------------------------------------------------------------------
  12. #!/usr/bin/env python
  13. #Boa:App:BoaApp
  14.  
  15. import wx
  16.  
  17. import SchedThreadTestFrame
  18.  
  19. modules ={u'SchedThreadTestFrame': [1,
  20.                             'Main frame of Application',
  21.                             u'SchedThreadTestFrame.py']}
  22.  
  23. class BoaApp(wx.App):
  24.     def OnInit(self):
  25.         self.main = SchedThreadTestFrame.create(None)
  26.         self.main.Show()
  27.         self.SetTopWindow(self.main)
  28.         return True
  29.  
  30. def main():
  31.     application = BoaApp(redirect=1)
  32.     application.MainLoop()
  33.  
  34. if __name__ == '__main__':
  35.     main()
  36.  
Nov 19 '07 #8

P: 7
Wow... that's a lot of work you put into it, thanks very much! I've resolved most of my issues with concurrency now (also thanks to discovering functions like wx.Yield(), wx.FutureCall() etc ;)

Greg
Nov 23 '07 #9

bartonc
Expert 5K+
P: 6,596
Wow... that's a lot of work you put into it, thanks very much! I've resolved most of my issues with concurrency now (also thanks to discovering functions like wx.Yield(), wx.FutureCall() etc ;)

Greg
wx.FutureCall() comes in very handy and I use it quite often to do initialization after the window has been created (and shown).

wx.Yield() has (for some reason) been declared deprecated:
::wxYield
bool wxYield()

Calls wxApp::Yield.

This function is kept only for backwards compatibility. Please use the wxApp::Yield method instead in any new code.
But Pythoneers rarely keep a reference to the app object, so you may want to keep an eye out for compatibility issues like this.
Nov 23 '07 #10

bartonc
Expert 5K+
P: 6,596
Wow... that's a lot of work you put into it, thanks very much! I've resolved most of my issues with concurrency now (also thanks to discovering functions like wx.Yield(), wx.FutureCall() etc ;)

Greg
You are quite welcome. Actually, it's no trouble at all (given that my IDE - Boa Constructor - did most of the writing).

Since you were able to discover wx.FutureCall(), then you must have the wxPython in Action book. I haven't been able to find documentation for that function anywhere else. If you have a different resource, I'd really like to know about it.

Thanks.
Nov 25 '07 #11

bartonc
Expert 5K+
P: 6,596
You are quite welcome. Actually, it's no trouble at all (given that my IDE - Boa Constructor - did most of the writing).

Since you were able to discover wx.FutureCall(), then you must have the wxPython in Action book. I haven't been able to find documentation for that function anywhere else. If you have a different resource, I'd really like to know about it.

Thanks.
This is ripped right out of the threading module in the Python lib directory:
Expand|Select|Wrap|Line Numbers
  1.  
  2. def Timer(*args, **kwargs):
  3.     return _Timer(*args, **kwargs)
  4.  
  5. class _Timer(Thread):
  6.     """Call a function after a specified number of seconds:
  7.  
  8.     t = Timer(30.0, f, args=[], kwargs={})
  9.     t.start()
  10.     t.cancel() # stop the timer's action if it's still waiting
  11.     """
  12.  
  13.     def __init__(self, interval, function, args=[], kwargs={}):
  14.         Thread.__init__(self)
  15.         self.interval = interval
  16.         self.function = function
  17.         self.args = args
  18.         self.kwargs = kwargs
  19.         self.finished = Event()
  20.  
  21.     def cancel(self):
  22.         """Stop the timer if it hasn't finished yet"""
  23.         self.finished.set()
  24.  
  25.     def run(self):
  26.         self.finished.wait(self.interval)
  27.         if not self.finished.isSet():
  28.             self.function(*self.args, **self.kwargs)
  29.         self.finished.set()
Nov 30 '07 #12

bartonc
Expert 5K+
P: 6,596
Here's the version after I developed are real need (running wxPython in a thread).
I'm considering adding wxEvents to this soon, but for now it's a close enough approximation to drop in as a replacement for wxTimer():
Expand|Select|Wrap|Line Numbers
  1. #-----------------------------------------------------------------------------
  2. # Name:        Timer.py
  3. # Purpose:     A replacement for wxTimer that will run in a thread
  4. #
  5. # Author:      Barton Cline
  6. #
  7. # Created:     2007/11/30
  8. # RCS-ID:      $Id: Timer.py $
  9. # Copyright:   (c) 2007
  10. # Licence:     <your licence>
  11. #-----------------------------------------------------------------------------
  12.  
  13. from threading import Thread, Event
  14. import sched
  15. from time import time, sleep
  16.  
  17. class Timer(object):
  18.  
  19. ##    def __del__(self):
  20. ##        """Useless! GC won't call here while the thread is running."""
  21. ##        # So find a way to detect the main thread reaching its termination #
  22. ##        # the threading module is hooked into the python.exitfunction mechanism,
  23. ##        # so it may be posible to do too much here.
  24. ##        self.Stop()
  25.  
  26.     def __init__(self, actionFunct, *args):
  27.         # The function to perform
  28.         self.actionFunct = actionFunct
  29.         # its arguments
  30.         self.args = args
  31.  
  32.         # Handles thread termination correctly #
  33.         self.oneShot = False
  34.  
  35.         # The scheduler:
  36.         self.timer = sched.scheduler(time, sleep)
  37.         self.event = None  #store the result of sched.scheduler.enter()
  38.         # a thread-safe way to signal the scheduler to stop
  39.         self.runEnableFlag = Event() # created in the clear()ed state
  40.  
  41.         # Need a thread to activate the scheduler because sched.scheduler.run() won't return
  42.         self.runThread = Thread(target=self.Run, args=(self.runEnableFlag, self.timer))
  43.  
  44.     def SimEvent(self):
  45.         """Call the function then reschedule this handler. The scheduler will block
  46.         further attempts at calling the actionFunct if an error occurres there."""
  47.         if self.oneShot:
  48.             self.runEnableFlag.clear()
  49.         # record the ammount of time taken by
  50.         startTime = time()
  51.         # the task that gets called
  52.         self.actionFunct(*self.args)
  53.         # scaled to milliseconds
  54.         timeSpent = (time() - startTime)/1000
  55.         if timeSpent > self.interval:
  56.             # just go again after the given interval
  57.             interval = self.interval
  58.         else:
  59.             # adjust for a precise rep-rate
  60.             interval = self.interval - timeSpent
  61.         if self.runEnableFlag.isSet():
  62.             self.event = self.timer.enter(interval, 1, self.SimEvent, ())
  63.  
  64.     def Run(self, runEnableFlag, timer):
  65.         """The target of the internal thread for starting the scheduler."""
  66.         runEnableFlag.set()
  67.         timer.run()
  68.         # if sched.scheduler.run() returns, clear the flag
  69.         runEnableFlag.clear()
  70.  
  71.     def Start(self, milliseconds, oneShot=False):
  72.         """Manage resetting of the interval by cancel()ing the sched.event"""
  73.         if milliseconds <= 0:
  74.             raise ValueError, "milliseconds must be greater that zero."
  75.         # interval is scaled to milliseconds
  76.         self.interval = float(milliseconds)/1000
  77.         self.oneShot = oneShot
  78.         # Are we being re-Start()ed? #
  79.         if (not self.event is None) and (not self.timer.empty()):
  80.             # Yes, so remove this event, if it has not completed
  81.             self.timer.cancel(self.event)
  82.         # enter a 1st priority task into the scheduler's queue
  83.         self.event = self.timer.enter(self.interval, 1, self.SimEvent, ())
  84.         if not self.runEnableFlag.isSet():
  85.             self.runThread.start()
  86.  
  87.     def Stop(self):
  88.         if (self.event is not None) and (not self.timer.empty()):
  89.             # remove this event, if it has not completed
  90.             self.timer.cancel(self.event)
  91.             # and let the start threat manage the flag
  92.         else:
  93.             self.runEnableFlag.clear()
  94.  
  95.     def IsRunning(self):
  96.         return self.runThread.isAlive()
  97.  
  98.     def GetInterval(self):
  99.         """Rescale and cast the interval to int before returning."""
  100.         return int(self.interval * 1000)
  101.  
  102. def ThreadSafeTest():
  103.     # test thread safety #
  104.     def BG_Task(thrdEvent):
  105.         def periodicTask(arg1, arg2):
  106.             print arg1, arg2,
  107.             print time()
  108.  
  109.         t = Timer(periodicTask, 'hello', 'world')
  110.         print 'starting bg at ', time()
  111.         t.Start(250)
  112.         print 'running bg'
  113.         thrdEvent.wait()
  114.         print 'stopping bg'
  115.         t.Stop()
  116.  
  117.     threadingEvent = Event()
  118.     thrd = Thread(target=BG_Task, args=(threadingEvent,))
  119.     print 'starting thread'
  120.     thrd.start()
  121.     sleep(2)
  122.     print 'stopping thread'
  123.     threadingEvent.set()
  124.     thrd.join()
  125.  
  126. if __name__ == "__main__":
  127.     def periodicTask(arg1, arg2):
  128.         print arg1, arg2,
  129.         print time()
  130.  
  131.     t = Timer(periodicTask, 'hello', 'world')
  132.     print 'starting bg at ', time()
  133.     t.Start(250)
  134.     print 'running bg at 250, but interupting and resetting to 125'
  135.     sleep(.76)
  136.     t.Start(125)
  137.     print 'running bg at 125'
  138.     sleep(.5)
  139.     # passed
  140.     print 'running one-shot'
  141.     t.Start(125, True)
  142. ##    # passed
  143. ##    print 'stopping bg'
  144. ##    t.Stop()
  145. ##    # XXX FAILED!
  146. ##    t = None # will it be garbage collected?
  147. ##    # XXX FAILED!
  148. ##    del t
Nov 30 '07 #13

P: 2
Thank you! I needed a way to generate timer driven events in a wxPython sub-thread and this works great!
Feb 1 '11 #14

Post your reply

Sign in to post your reply or Sign up for a free account.