2

i am a little new to asyncio in python. I was trying to run this simple code but i don't know why i am getting this unexpected output.

What i did is that, in outer function, i created async tasks and stored it in an array tasks. Before awaiting on these tasks i wrote a print statement print("outer") that should run in every iteration. And inside the task i wrote another print statement print("inner") in inner function. But some how i am getting some unexpected output.

Here's the code -

import asyncio


def main():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(outer(loop))
    loop.close()


async def outer(loop):
    tasks = []
    for i in range(0, 5):
        tasks.append(loop.create_task(inner()))

    for task in tasks:
        print("outer")
        await task


async def inner():
    print("inner")
    await asyncio.sleep(0.5)

if __name__ == '__main__':
    main()

Here's the output -

outer
inner
inner
inner
inner
inner
outer
outer
outer
outer

My Expected output was -

outer
inner
outer
inner
outer
inner
outer
inner
outer
inner

Why all the inner are printing before outer. What is the correct execution flow of asyncio. Thanks in advance.

3
  • Each task/coroutine runs up to the await statement then it gets suspended.. Your example is similar to the coroutine example in the docs which has similar results. Commented Mar 29, 2020 at 20:24
  • From the docs - While a Task is running in the event loop, no other Tasks can run in the same thread. When a Task executes an await expression, the running Task gets suspended, and the event loop executes the next Task. Commented Mar 29, 2020 at 20:37
  • But when one task gets awaited, then for starting a new task, event loop has to execute the next for loop iteration. If that is true, then outer should have be printed. Commented Mar 30, 2020 at 9:49

2 Answers 2

3

In async def outer(loop)

for i in range(0, 5):
    tasks.append(loop.create_task(inner()))
  • Five new inner tasks are created, scheduled and start running.
  • the event loop runs the scheduled tasks till they are completed.

If you add a little bit more to to inner and outer it shows this process better:

async def outer(loop):
    tasks = []
    for i in range(0, 5):
        tasks.append(loop.create_task(inner(i)))
    await asyncio.sleep(3)
    for task in tasks:
        print('outer')
        await task

async def inner(n):
    print(f"inner {n} start")
    await asyncio.sleep(0.5)
    print(f'inner {n} end')
  • while outer is sleeping

    • The first task runs to its await statement
    • The event loop suspends execution of the first task
    • The event loop runs the next scheduled task up to its await statement
    • This continues till each task has had a chance to run up to its await statement then the event loop starts looking around to see if any of the tasks are done waiting for whatever they were waiting for - if done it lets them run some more.
    • This continues till all the tasks are done
  • You can see that the five tasks execute and finish before the second for loop even starts.

  • in the second for loop each task has already completed so await task has nothing to wait for and outer is printed five times in succession.

I'm a little hazy on the event loop controlling everything - I haven't found any explicit documentation for that - probably alluded to in the create_task docs: Wrap the coro coroutine into a Task and schedule its execution. When you create the task it gets scheduled. I have seen videos on pyvideo.org that shows that process, unfortunately I couldn't quickly find the one I wanted to link to.

Sign up to request clarification or add additional context in comments.

2 Comments

Did you find that pyvideo.org link where the complete process is explained?
@shikharsrivastava it eludes me. There are too many to watch/re-watch to find it. I may even be misremembering parts of many - thinking they are one.
2

The loop.create_task(inner()) immediately queues all 5 inner tasks for running, not await task. The await task suspends outer until the first task is completely done, i.e. at least 0.5 seconds. During this, all inner tasks have been run once up await, including their print.


async def outer(loop):
    tasks = []
    for i in range(0, 5):
        # queues task immediately
        tasks.append(loop.create_task(inner()))

    for task in tasks:
        print("outer")
        # suspends for at least 0.5 seconds
        await task


async def inner():
    # runs immediately
    print("inner")
    await asyncio.sleep(0.5)

1 Comment

aha!! If you add await asyncio.sleep(10); print('outer done sleeping' just after the loop that creates the tasks you can see that they run before any are awaited. thnx

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.