14

I'm trying to wrap my head around async/await in python.

Am I on the right track?

  • async and @coroutine functions returns coroutine/generator, not the returned value.
  • await extracts the actual return value of coroutine/generator.
     

  • async function result (coroutines) is meant to be added to event-loop.

  • await creates "bridge" between event-loop and awaited coroutine (enabling the next point).
  • @coroutine's yield communicates directly with event-loop. (skipping direct caller which awaits the result)
     

  • await can be used only inside async functions.

  • yield can be used only inside @coroutine.

(@coroutine = @types.coroutine)

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
industryworker3595112
  • 3,419
  • 2
  • 14
  • 18

2 Answers2

16

async and @coroutine functions returns coroutine/generator, not the returned value

To be technical, types.coroutine returns a generator-based coroutine which is different than generators and different than coroutines.

await extracts the actual return value of coroutine/generator.

await, similar to yield from, suspends the execution of the coroutine until the awaitable it takes completes and returns the result.

async function result (coroutines) is meant to be added to event-loop.

Yes.

await creates "bridge" between event-loop and awaited coroutine (enabling the next point).

await creates a suspension point that indicates to the event loop that some I/O operation will take place thereby allowing it to switch to another task.

@coroutine's yield communicates directly with event-loop. (skipping direct caller which awaits the result)

No, generator-based coroutines use yield from in a similar fashion to await, not yield.

await can be used only inside async functions.

Yes.

yield can be used only inside coroutine.

yield from can be used inside generator-based coroutines (generators decorated with types.coroutine) and, since Python 3.6, in async functions that result in an asynchronous generator.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • 1
    I investigated further. In answer https://stackoverflow.com/a/46462604/3595112, when `inner_coro` does `yield`, control flow goes to event loop, skipping initial caller `outer_async`. Given this example, would you agree that here `yield` is a way for coroutine to communicate directly with event loop? – industryworker3595112 Sep 28 '17 at 06:51
0

Demo code:

(illustrates whole control flow between async and types.coroutine and event loop)

import types


class EL:
    """Fake An event loop."""

    def __init__(self, outer_async):
        self.outer_async = outer_async

    def loop(self):
        print('    EL.loop : outer_async.send(None)')
        send_result = self.outer_async.send(None) # seed outer_async.
        print('    EL.loop : outer_async.send(None) -> outer_async_send_result = {}'.format(send_result))

        do_loop = True
        loop_counter = 0

        while do_loop:
            print()
            loop_counter += 1
            try:
                arg = send_result + '-loop(send-{})'.format(loop_counter)
                print('    EL.loop.while : task.outer_async.send({})'.format(arg))
                send_result = self.outer_async.send(arg) # raises StopIteration.
                print('    EL.loop.while : task.outer_async.send({}) -> send_result = {}'.format(arg, send_result))
            except StopIteration as e:
                print('    EL.loop.while : except StopIteration -> {}'.format(e.value))
                do_loop = False
        return loop_counter


async def outer_async(label):
    inner_coro_arg = label + '-A1'
    print('        outer_async({}) : await inner_coro({})'.format(label, inner_coro_arg))
    await_result = await inner_coro(inner_coro_arg)
    print('        outer_async({}) : await inner_coro({}) -> await_result = {}'.format(label, inner_coro_arg, await_result))

    inner_coro_arg = label + '-A2'
    print('        outer_async({}) : await inner_coro({})'.format(label, inner_coro_arg))
    await_result = await inner_coro(inner_coro_arg)
    print('        outer_async({}) : await inner_coro({}) -> await_result = {}'.format(label, inner_coro_arg, await_result))
    return 555555


@types.coroutine
def inner_coro(inner_coro_label):
    yld_arg = inner_coro_label + '-C(yield)'
    print('            inner_coro({}) : yield({})'.format(inner_coro_label, yld_arg))
    yield_result = yield yld_arg
    print('            inner_coro({}) : yield({}) -> yield_result = {}'.format(inner_coro_label, yld_arg, yield_result))
    return_value = yield_result + '-C(return)'
    print('            inner_coro({}) : return -> {}'.format(inner_coro_label, return_value))
    return return_value


def main():
    loop = EL(outer_async('$$'))
    print('main() : loop.loop')
    loop_outer_async = loop.loop()
    print('main() : loop.loop -> {}'.format(loop_outer_async))


if __name__ == '__main__':
    main()

Result:

main() : loop.loop
    EL.loop : outer_async.send(None)
        outer_async($$) : await inner_coro($$-A1)
            inner_coro($$-A1) : yield($$-A1-C(yield))
    EL.loop : outer_async.send(None) -> outer_async_send_result = $$-A1-C(yield)

    EL.loop.while : task.outer_async.send($$-A1-C(yield)-loop(send-1))
            inner_coro($$-A1) : yield($$-A1-C(yield)) -> yield_result = $$-A1-C(yield)-loop(send-1)
            inner_coro($$-A1) : return -> $$-A1-C(yield)-loop(send-1)-C(return)
        outer_async($$) : await inner_coro($$-A1) -> await_result = $$-A1-C(yield)-loop(send-1)-C(return)
        outer_async($$) : await inner_coro($$-A2)
            inner_coro($$-A2) : yield($$-A2-C(yield))
    EL.loop.while : task.outer_async.send($$-A1-C(yield)-loop(send-1)) -> send_result = $$-A2-C(yield)

    EL.loop.while : task.outer_async.send($$-A2-C(yield)-loop(send-2))
            inner_coro($$-A2) : yield($$-A2-C(yield)) -> yield_result = $$-A2-C(yield)-loop(send-2)
            inner_coro($$-A2) : return -> $$-A2-C(yield)-loop(send-2)-C(return)
        outer_async($$) : await inner_coro($$-A2) -> await_result = $$-A2-C(yield)-loop(send-2)-C(return)
    EL.loop.while : except StopIteration -> 555555
main() : loop.loop -> 2
industryworker3595112
  • 3,419
  • 2
  • 14
  • 18
  • 7
    This istoo complex. – tushortz Jul 25 '18 at 06:51
  • what this code demonstrate it that: yield will generate the result and **hang** the coroutine to the `send` method, however, await not, notice that there two continue await function run. ``` – logan Dec 03 '19 at 16:36