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')?
7
  • 1
    In your case, just add await asyncio.gather(task1, task2) at the end of main. Commented Feb 13, 2022 at 9:40
  • The part I don't understand is why are the tasks pending, is it because of await? Does await cause the task to 'pend'?? Commented Feb 13, 2022 at 9:41
  • 1
    They are pending because you started them and then the program ends before they are done. Commented Feb 13, 2022 at 9:41
  • Okay! But how do we know when the program ends?? Commented Feb 13, 2022 at 9:42
  • Is it after a certain number of seconds? Or because it hit an 'await'? Commented Feb 13, 2022 at 9:42

2 Answers 2

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.

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

1 Comment

This is exactly what I was asking- thank you Andrew it is clear now
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.

7 Comments

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?
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?
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()
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'
e.g. does it print just one line of each function by default? Because that's what it seems like
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.