1

I am just starting to learn about asynchronous programming, in particular the async and await syntax and usage of the asyncio module.

My question is regarding the output of the following code:

import asyncio


async def foo():
    print('start fetching')
    await asyncio.sleep(2)
    print('done fetching')
    return {'data':1}

async def boo():
    for i in range(10):
        print(i)
        await asyncio.sleep(0.25)


async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(boo())

asyncio.run(main())

The output that I get is:

start fetching
0

While I understand what each individual part of the code does, I can't understand why only two lines of the code is being outputted. Here is my understanding:

  • We start with task1 and print start fetching.
  • Then we hit the await for 2 seconds.
  • In these two seconds doesn't this give task2 the opportunity to do its execution for 2 seconds, so 0-8 will be printed?
  • Then I also don't see why we don't continue executing the function foo()? – i.e. print('done fetching')?
Andrew Svetlov
  • 16,730
  • 8
  • 66
  • 69
Patrick_Chong
  • 434
  • 2
  • 12

2 Answers2

3

asyncio.run() has two extra event loop iterations internally, they are used for shutdown:

    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.run_until_complete(loop.shutdown_default_executor())

Running these calls gives previously scheduled tasks a chance to iterate over up to 2 times.

Note: this is an implementation detail that may be changed at any time (the number of iterations for startup/shutdown of the event loop can be modified in future Python versions).

Please don't rely on this behavior in your code, but explicitly await scheduled tasks to correctly process them.

Andrew Svetlov
  • 16,730
  • 8
  • 66
  • 69
1

You must wait your tasks to finish:

async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(boo())

    await task1
    await task2

Output:

start fetching
0
1
2
3
4
5
6
7
done fetching
8
9

See the official document for more examples.


Now let's talk about why it only print one line of each function. The key is any block operation will lead to task rescheduling:

import asyncio

async def foo():
    print('start fetching')
    # Do not await anything
    print('done fetching')
    i = 0
    # Compute for a long time, but the task scheduler
    # will not interrupt it.
    while i < 99999999:
        i += 1
    return {'data':1}

async def boo():
    for i in range(10):
        print(i)
        # Do not await anything

async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(boo())

asyncio.run(main())

Output:

start fetching
done fetching
0
1
2
3
4
5
6
7
8
9

As you can see, these tasks completed in sequence.

async def foo():
    print('start fetching')
    # Although it actually not sleep,
    # it still force the scheduler to switch tasks.
    await asyncio.sleep(0)
    print('done fetching')
    return {'data':1}

async def boo():
    for i in range(10):
        print(i)

async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(boo())
    await asyncio.sleep(0)

asyncio.run(main())

Output:

start fetching
0
1
2
3
4
5
6
7
8
9
done fetching

If any task is not completed, while the script terminated, those uncompleted tasks will be discarded.

If you want to dive deeper into the scheduler, maybe you should read the source code of asyncio.

  • BubbleQuote, thanks for your answer. But it still doesn't quite answer my question- I understand how I would do it to achieve what I want using await. But what I don't understand is that FOR MY GIVEN CODE ABOVE, WHY does it only output the 2 lines? – Patrick_Chong Feb 12 '22 at 11:02
  • My question is more about the execution of the main() function, and why when calling on task1 and task2 doesn't the execution of task1 and task2 fully finish? And instead only the 2 lines of code are outputted? – Patrick_Chong Feb 12 '22 at 11:03
  • If you do not `await` them, you can not make sure they are completed. On the other hand, these tasks may also start even if you do not `await` them. To verify this, just append `await asyncio.sleep(5)` in your `main()` – BubbleQuote Feb 12 '22 at 15:23
  • Bubble, I am not arguing that await is not necessary. I am asking why my code above only prints 2 lines.. I want to understand how the running of the code works if we don't add the 'await' – Patrick_Chong Feb 12 '22 at 19:08
  • e.g. does it print just one line of each function by default? Because that's what it seems like – Patrick_Chong Feb 12 '22 at 19:08
  • Or does the await inside the function cause it to break? – Patrick_Chong Feb 12 '22 at 19:10
  • It launches the tasks but then exits main() before those are finished. You only have enough time to execute the first line of each before main exits. – Ivan May 21 '22 at 12:05