c# async, await
Ian Kelly
ian.g.kelly at gmail.com
Fri Aug 23 00:11:19 EDT 2013
On Thu, Aug 22, 2013 at 5:29 AM, Neal Becker <ndbecker2 at gmail.com> wrote:
> So my son is now spending his days on c# and .net. He's enthusiastic about
> async and await, and said to me last evening, "I don't think python has anything
> like that". I'm not terribly knowledgeable myself regarding async programming
> (since I never need to use it). I did look at this:
>
> http://tirania.org/blog/archive/2013/Aug-15.html
>
> I wonder what response the python community might have.
I've done something sort of similar to await in Python using
restartable functions. The code looks like this (using Twisted
Deferreds, but any sort of promise could be substituted in):
from functools import wraps
from twisted.internet import defer
def restartable(func):
def resume(result, is_failure, results, args, kws):
def await(get_deferred, *args, **kws):
try:
is_failure, result = reversed_results.pop()
except IndexError:
raise Await(get_deferred(*args, **kws))
if is_failure:
result.raiseException()
return result
def do_once(func, *args, **kws):
return await(defer.maybeDeferred, func, *args, **kws)
await.do_once = do_once
if results is None:
results = []
else:
results.append((is_failure, result))
reversed_results = list(reversed(results))
try:
func(await, *args, **kws)
except Await as exc:
deferred = exc.args[0]
deferred.addCallback(resume, False, results, args, kws)
deferred.addErrback(resume, True, results, args, kws)
@wraps(func)
def wrapper(*args, **kws):
return resume(None, None, None, args, kws)
return wrapper
class Await(BaseException): pass
The usage of restartable and await then looks something like this:
@restartable
def random_sum(await):
try:
a = await(random_number)
b = await(random_number)
c = await(random_number)
d = await(random_number)
except ValueError as exc:
print("Couldn't get four numbers: " + exc.message)
return
print('{} + {} + {} + {} = {}'.format(a, b, c, d, a + b + c + d))
The "await" argument is passed in by the restartable machinery, not by
the caller. The argument passed to await is a callable that is
expected to return a Deferred, and any additional arguments are passed
along to the callable. A boring implementation of the "random_number"
callable might look like this:
def random_number():
from random import randrange
from twisted.internet import defer, reactor
deferred = defer.Deferred()
if randrange(4) > 0:
number = randrange(42)
print("Generated {}".format(number))
reactor.callLater(1, deferred.callback, number)
else:
print("Failed")
reactor.callLater(1, deferred.errback, ValueError("Not available"))
return deferred
Of course the big caveat to all this is that since the function is
restartable, the "random_sum" function above actually gets called five
times, and so if there are any side effects before the last await,
they'll end up happening multiple times. This can be averted using
await.do_once:
@restartable
def random_sum(await):
try:
await.do_once(print, 1)
a = await(random_number)
await.do_once(print, 2)
b = await(random_number)
await.do_once(print, 3)
c = await(random_number)
await.do_once(print, 4)
d = await(random_number)
except ValueError as exc:
print("Couldn't get four numbers: " + exc.message)
return
print('{} + {} + {} + {} = {}'.format(a, b, c, d, a + b + c + d))
The result of running this is:
1
Generated 35
2
Generated 28
3
Generated 32
4
Generated 16
35 + 28 + 32 + 16 = 111
More information about the Python-list
mailing list