| # 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 _thread as thread |
| import 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.__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() |
| |
| class Killed(Exception): pass |
| class EarlyExit(Exception): pass |
| |
| 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: |
| 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 target not in self.invokedby: |
| 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 |