How awaitable object is executed

Hi,

Given that I have

async def do_request():
    # do something

I know that, we can only call await do_request() inside another async function. But we always have a sync function at the beginning. In the application, we start to transition from sync to async when we call loop.run_until_complete, or trio.run.

My question is, under loop.run_until_complete and trio.run, how it run the awaitable without await (in the sync context)?

Async code actually internally works in a similar way to generators and yield. An awaitable implements an interface almost identical to iterators, except it’s got different method names to distinguish between the protocols. async def makes the function return an awaitable, which can have __anext__() called to execute part of it up to the next await call. That itself acts sorta like yield from, in that it then passes through the __anext__ calls, until that awaitable terminates.

The role of the async lib’s run() function is to contain a while loop, which picks tasks that are ready to execute and calls anext() to run them. When you call library methods that do things like sleep, do IO, schedule tasks etc those functions don’t call await, but instead “yield” back some special value. That passes back through the call chain, and then the run function receives it and is able to use it to decide how the task should be paused - for instance to only re-run after the delay has elapsed, or the I/O has completed, etc.

1 Like