''' Classes for controlling groups of threads that execute
      tasks periodically.

      The problem with simpler ways of launching and killing
      sets of threads that do repetitive tasks is that one
      usually uses time.sleep to control the repetition (or
      polling), leading to a delay in killing each thread.

      The approach used here is to start a single control
      thread that frequently checks a flag variable.  When
      the variable is false, the control thread exits. All
      the other threads in the group are waiting until
      either a time increment is over or the control thread
      dies; they loop as soon as the latter occurs, so they
      immediately see the changed flag variable and exit.
'''

# 2002/08/22 EF

import time
from threading import Thread

class controlThread(Thread):
   def __init__(self, Tgroup):  # Tgroup to distinguish from group in Thread
      Thread.__init__(self)
      self.setDaemon(1)
      self.Tgroup = Tgroup

   def run(self):
      while self.Tgroup.running:
         time.sleep(0.2)



class ThreadGroup:
   def __init__(self):
      self.running = 1
      self.cThread = controlThread(self)
      self.cThread.start()
      self.threadList = []

   def timer(self, t):
      self.cThread.join(t)

   def add(self, T):
      ''' Register a new thread T in the group. '''
      self.threadList.append(T)

   def start(self):
      ''' Start all threads currently registered. '''
      for T in self.threadList:
         T.start()

   def stop(self):
      ''' Signal all threads to stop, and return when all
      have stopped.'''
      self.running = 0
      while len([1 for T in self.threadList if T.isAlive()]) > 0:
         time.sleep(0.05)


class subThread(Thread):
   ''' This is like a regular thread, except that the
   target is a function that will be executed repeatedly
   by a loop inside the run method.
   '''
   def __init__(self, Tgroup, timeout = 1, target = None, *args, **kw):
      Thread.__init__(self, *args, **kw)
      self.setDaemon(1)  # So it will always exit if main thread ends.
      self.timeout = timeout
      self.Tgroup = Tgroup
      self.timer = lambda t: Tgroup.cThread.join(t)
      self.__target = target
      self.__args = ()
      if kw.has_key('args'): self.__args = kw['args']
      self.__kwargs = {}
      if kw.has_key('kwargs'): self.__kwargs = kw['kwargs']

   def run(self):
      self.name = self.getName()
      timeout = self.timeout
      fn = self.__target
      args = (self,) + self.__args
      kwargs = self.__kwargs
      while self.Tgroup.running:
         apply(fn, args, kwargs)
         self.timer(timeout)
      #print "%s ending" % self.name


# Test; illustration of how to use the ThreadGroup and subThread
if __name__ == "__main__":
   from Tkinter import Tk, Button

   def fn1(self, otherstuff, dict_arg1, dict_arg2):
      print self.name
      print otherstuff
      print dict_arg1 + dict_arg2

   def fn2(self):
      print self.name
   tg = ThreadGroup()
   tg.add(subThread(name = "test_1", target = fn2, Tgroup = tg, timeout = 3))
   tg.add(subThread(name = "test_2", target = fn1, Tgroup = tg, timeout = 2,
                    args = ('otherstuff--args',),
                    kwargs = {'dict_arg1': 'DictArg1',
                              'dict_arg2': 'DictArg2'}))
   tg.add(subThread(name = "test_3", target = fn2, Tgroup = tg, timeout = 4))
   tg.start()


   def quit():
      tg.stop()
      raise SystemExit

   root = Tk()
   root.option_add('*Button.activeBackground', 'yellow')
   b = Button(root, text = "test", command = quit)
   b.pack()
   root.mainloop()

