Search code examples
pythonpython-asyncio

Python asyncio confused about await and tasks


Complete newb here, reading about Asycnio Tasks which has this example:

import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    print(f"started at {time.strftime('%X')}")

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

My original understanding of await is that it blocks the execution of current function and wait for the async function to return.

But in this case both coroutines are executed concurrently, it doesn't fit well my understanding of await. Could someone please explain?

Further investigation, by adding extra print in say_after, it seems to me the coroutine doesn't start until await happens...

import asyncio
import time

async def say_after(delay, what):
    print('Received {}'.format(what))
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    print(f"started at {time.strftime('%X')}")

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

prints

started at 13:41:23
Received hello
Received world
hello
world
finished at 13:41:25

Solution

  • When you encapsulate a coroutine in a Task (or Future) object the coroutine is ready to work, so when the event loop start running on the first await, both task1 and task2 are running.

    Trying to make it clearer, to execute a coroutine you need two things:
    1) a coroutine incapsulated in a future object(Task) to make it awaitable
    2) a running event loop

    In your example the execution works like this:
    1 - create_task1
    2 - create_task2
    3 - await task1
    4 - await sleep of task1
    5 - await sleep of task2

    now both task1 and task2 are sleeping so, suppose task1 is the first to finish (sleep some time)

    6 - print of task1
    7 - await task2
    8 - print of task2

    now the loop end

    As you said when you got an await the execution stops, but let me say that stops just in the current "execution flow", when you create a future(Task) you create another exucution flow, and the await just switch to the current execution flow. This last explanation is not completely correct in sens of terms but help to make it clearer.

    I hope i was clear. P.S.: sorry for my bad english.