159 lines · 5.4 KB · Python
RawDownload
# Coroutine implementation using Python threads.## Combines ideas from Guido's Generator module, and from the coroutine# features of Icon and Simula 67.## To run a collection of functions as coroutines, you need to create# a Coroutine object to control them:#    co = Coroutine()# and then 'create' a subsidiary object for each function in the# collection:#    cof1 = co.create(f1 [, arg1, arg2, ...]) # [] means optional,#    cof2 = co.create(f2 [, arg1, arg2, ...]) #... not list#    cof3 = co.create(f3 [, arg1, arg2, ...])# etc.  The functions need not be distinct; 'create'ing the same# function multiple times gives you independent instances of the# function.## To start the coroutines running, use co.tran on one of the create'd# functions; e.g., co.tran(cof2).  The routine that first executes# co.tran is called the "main coroutine".  It's special in several# respects:  it existed before you created the Coroutine object; if any of# the create'd coroutines exits (does a return, or suffers an unhandled# exception), EarlyExit error is raised in the main coroutine; and the# co.detach() method transfers control directly to the main coroutine# (you can't use co.tran() for this because the main coroutine doesn't# have a name ...).## Coroutine objects support these methods:## handle = .create(func [, arg1, arg2, ...])#    Creates a coroutine for an invocation of func(arg1, arg2, ...),#    and returns a handle ("name") for the coroutine so created.  The#    handle can be used as the target in a subsequent .tran().## .tran(target, data=None)#    Transfer control to the create'd coroutine "target", optionally#    passing it an arbitrary piece of data. To the coroutine A that does#    the .tran, .tran acts like an ordinary function call:  another#    coroutine B can .tran back to it later, and if it does A's .tran#    returns the 'data' argument passed to B's tran.  E.g.,##    in coroutine coA   in coroutine coC    in coroutine coB#      x = co.tran(coC)   co.tran(coB)        co.tran(coA,12)#      print x # 12##    The data-passing feature is taken from Icon, and greatly cuts#    the need to use global variables for inter-coroutine communication.## .back( data=None )#    The same as .tran(invoker, data=None), where 'invoker' is the#    coroutine that most recently .tran'ed control to the coroutine#    doing the .back.  This is akin to Icon's "&source".## .detach( data=None )#    The same as .tran(main, data=None), where 'main' is the#    (unnameable!) coroutine that started it all.  'main' has all the#    rights of any other coroutine:  upon receiving control, it can#    .tran to an arbitrary coroutine of its choosing, go .back to#    the .detach'er, or .kill the whole thing.## .kill()#    Destroy all the coroutines, and return control to the main#    coroutine.  None of the create'ed coroutines can be resumed after a#    .kill().  An EarlyExit exception does a .kill() automatically.  It's#    a good idea to .kill() coroutines you're done with, since the#    current implementation consumes a thread for each coroutine that#    may be resumed. import threadimport sync class _CoEvent:    def __init__(self, func):        self.f = func        self.e = sync.event()     def __repr__(self):        if self.f is None:            return 'main coroutine'        else:            return 'coroutine for func ' + self.f.func_name     def __hash__(self):        return id(self)     def __cmp__(x,y):        return cmp(id(x), id(y))     def resume(self):        self.e.post()     def wait(self):        self.e.wait()        self.e.clear() Killed = 'Coroutine.Killed'EarlyExit = 'Coroutine.EarlyExit' class Coroutine:    def __init__(self):        self.active = self.main = _CoEvent(None)        self.invokedby = {self.main: None}        self.killed = 0        self.value  = None        self.terminated_by = None     def create(self, func, *args):        me = _CoEvent(func)        self.invokedby[me] = None        thread.start_new_thread(self._start, (me,) + args)        return me     def _start(self, me, *args):        me.wait()        if not self.killed:            try:                try:                    apply(me.f, args)                except Killed:                    pass            finally:                if not self.killed:                    self.terminated_by = me                    self.kill()     def kill(self):        if self.killed:            raise TypeError, 'kill() called on dead coroutines'        self.killed = 1        for coroutine in self.invokedby.keys():            coroutine.resume()     def back(self, data=None):        return self.tran( self.invokedby[self.active], data )     def detach(self, data=None):        return self.tran( self.main, data )     def tran(self, target, data=None):        if not self.invokedby.has_key(target):            raise TypeError, '.tran target %r is not an active coroutine' % (target,)        if self.killed:            raise TypeError, '.tran target %r is killed' % (target,)        self.value = data        me = self.active        self.invokedby[target] = me        self.active = target        target.resume()         me.wait()        if self.killed:            if self.main is not me:                raise Killed            if self.terminated_by is not None:                raise EarlyExit, '%r terminated early' % (self.terminated_by,)         return self.value # end of module