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