pythonpython-asyncio

why _run_once won't called when custom coroutine called?


What I did

async def inner() -> None:
    print("inner")


async def main() -> None:
    print("main start")
    await inner()
    print("main end")


if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

and I added some print() in asyncio's event loop

    def run_forever(self):
        """Run until stop() is called."""

        try:
            self._thread_id = threading.get_ident()
            sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook,
                                   finalizer=self._asyncgen_finalizer_hook)

            events._set_running_loop(self)
            print("start loop in run_forever"). # here!
            while True:
                self._run_once()
                if self._stopping:
                    break



    def _run_once(self):
        """Run one full iteration of the event loop.

        This calls all currently ready callbacks, polls for I/O,
        schedules the resulting callbacks, and finally schedules
        'call_later' callbacks.
        """
        print("_run_once called!")  # here!
        sched_count = len(self._scheduled)
        if (sched_count > _MIN_SCHEDULED_TIMER_HANDLES and
        ...

What I thought would happen

  1. coroutine main() be called by event loop
  2. coroutine inner() is registered to event loop
  3. event loop calls inner() in _run_once()

What actually happend

  1. main() got called by event loop (by _run_once())
  2. main calls inner()
  3. inner() finishes (there was no interaction from event loop)

log

start loop in run_forever
_run_once called!
main start
inner
main end
_run_once called!
start loop in run_forever
_run_once called!
_run_once called!
start loop in run_forever
_run_once called!
_run_once called!

The question

  1. why this works like this?
  2. what actually calls the coroutine inner()?

Solution

  • Why there are three "start loop in run_forever" messages?

    The loop is started 3 times in asyncio.run (see the source code in asyncio/runners.py):

    Why is the whole program executed in one loop iteration? (Hoping I got the question right)

    await another_coro() does not interact directly with the event loop. It is very similar to yield from, i.e. it builds a bi-directional connection with the another_coro() and the program continues. If that coroutine awaits yet another coroutine etc., a pipeline of all these yield from-s is formed. Only await future_not_done_yet interacts with the loop, because it sends that future through that pipeline to the event loop (very similar to yield) and the loop reacts: it marks the current task as waiting for the just received future and schedules (with call_soon) another task from the set of runnable tasks to be run during the next loop iteration.