Search code examples
pythonpython-3.xasync-awaitpython-asynciosleep

Why is the output of an async function printed before that function is called?


The behavior of async functions is different even though they are almost the same code, but I don't understand why.

My code is:

import asyncio

async def func1():
    await asyncio.sleep(1)
    print("func1")

async def func2():
    await asyncio.sleep(time)
    print("func2")

async def func3():
    await asyncio.sleep(3)
    print("func3")

async def main():
    asyncio.create_task(func1())
    await func2()
    await func3()

asyncio.run(main())

If I put time = 1 then the output is:

func2

func1

func3

But if time = 2 then the output is:

func1

func2

func3

In the first case, why is func2 printed before func1 even though func2 starts late?


Solution

  • No matter how you get time into func2, the difference is clearly between how you start func1 and func2:

    async def main():
        asyncio.create_task(func1())
        await func2()
        ...
    

    You create the task, and then instantly await func2. Then, when func2 goes to sleep, func1 has a chance to start, and also goes to sleep.

    If func2 finds that time == 1, it wakes up after one second and prints, and then after that func1 wakes up, just a fraction later.

    If func2 finds that time == 2, it only wakes up after two seconds, and func1 will have woken up before it.

    Your question was "In the first case, why is func2 printed before func1 even though func2 starts late?" - you assume func2 "starts late", but it doesn't. It is awaited before the task created with func1. You never explicitly await the task, and your program could complete without the task ever completing, but since you have func3 there, waiting substantially longer, you're almost guaranteed it will.

    You would have found the order doesn't change when you do this:

    async def main():
        await func1()
        await func2()
        ...
    

    Because then the two asynchronous functions are awaited in order, and will complete in order when provided with the same sleep() time.

    Similarly, with:

    async def main():
        t = asyncio.create_task(func1())
        await t
        await func2()
        ...
    

    The order is as you expect it, since the task is explicitly awaited before func2() is started and awaited.

    Or of course, just:

    async def main():
        await asyncio.create_task(func1())
        await func2()
        ...
    

    A good editor or IDE will warn you, if you don't await create_task. PyCharm warns: "Co-routine create_task is not awaited".

    The documentation has this to say: "Important: Save a reference to the result of this function, to avoid a task disappearing mid-execution. The event loop only keeps weak references to tasks. A task that isn’t referenced elsewhere may get garbage collected at any time, even before it’s done. For reliable “fire-and-forget” background tasks, gather them in a collection."